# Kontinent AI: the API reference in one file This file is the complete API reference: the base URL, authentication, every endpoint, the live model catalogue with prices, the error contract, and every page of the developer docs. Generated: 2026-10-30T18:00:00Z --- **The prices below were live at that instant.** They are rendered from the registry on every request, not baked in — but this text is a copy, and a copy ages. `GET https://api.kontinentai.com/v1/models` is the authority and needs no API key. Re-fetch this URL rather than committing it to a repo. --- ## Start here - **Three request formats on one host.** `POST /v1/chat/completions` (OpenAI chat completions), `POST /v1/messages` and `POST /v1/messages/count_tokens` (Anthropic Messages), and `POST /v1/responses` (OpenAI Responses). Each model works only in the formats listed for it in the endpoint table below and in `formats` on `GET /v1/models`. - **Base URL:** `https://api.kontinentai.com/v1` for the OpenAI SDKs, Codex, OpenCode, OpenClaw and Hermes Agent. `https://api.kontinentai.com` (no `/v1`) for the Anthropic SDKs and Claude Code. - **Auth:** the key (`sk-kont-...`) in `Authorization: Bearer` or in `x-api-key`, on every endpoint that costs money. Server-side only: `/v1` sends no CORS headers. - **No Kontinent AI SDK exists.** The OpenAI and Anthropic SDKs work with a new base URL and key. - **Pricing rule:** each model costs a fixed fraction of its maker's official price, per token. Input, output and cached input use the same fraction. `GET /v1/models` returns our price, the list price (`pricing.list`) and the fraction (`pricing.multiple_of_list`) for each model. - **Balance is prepaid, in US dollars.** Costs are reported in micro-dollars, where 1 micros = $0.000001. - **Every response carries `x-request-id`.** Every response without a stream also has a `kontinent` block: `request_id`, `cost_micros` (what the call cost, the figure of record), `cost_usd`, `list_price_usd` (the same tokens at the official price) and `saved_usd`. A streamed chat completion carries the block on its `include_usage` frame. A streamed Messages or Responses response carries no block; the cost is recorded and the dashboard shows it. - **Balance:** a starter balance at signup, then redemption codes. Balance cannot be bought on the site at this time. ### The error envelope Errors from `/v1/chat/completions`, `/v1/responses` and `/v1/models` have OpenAI's shape. Errors from `/v1/messages` have Anthropic's shape (`{"type":"error","error":{"type","message"}}`), with `billing_error` in place of `insufficient_quota`. The OpenAI shape: ```json { "error": { "message": "Insufficient balance: $0.001230 required, $0.000400 available", "type": "insufficient_quota", "param": null, "code": "INSUFFICIENT_BALANCE", "details": { "required_micros": 1230, "required_usd": 0.00123, "available_micros": 400, "available_usd": 0.0004 } } } ``` `type` is OpenAI's vocabulary and is what the SDKs branch on. `code` is ours, stable, and safe to switch on. **An empty balance is 402, not 429** — do not treat it as a rate limit and retry; it needs more balance. The full status table is in the Errors page below. ### Machine-readable, no key required - `GET https://kontinentai.com/agent-setup/prompt.md`: setup instructions for an AI coding agent. Follow it to get the user's key, write the config of their tool and check it. - `GET https://api.kontinentai.com/v1/models` — the live catalogue, with `formats` per model. - `GET https://api.kontinentai.com/openapi.json` — OpenAPI 3.1, generated from the schemas the API actually validates against. --- ## The catalogue Text models. Prices are USD per million tokens: ours first, then the maker's official price, then the multiple (how many times less than the official price). | Model | Maker | Context | Input $/M | Output $/M | Official input $/M | Official output $/M | Multiple | Can do | Tier | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | | `claude-opus-5-5` | Anthropic | 200,000 | $0.8 | $4 | $4 | $20 | 5x less | tools, images | frontier | | `claude-opus-5` | Anthropic | 200,000 | $1 | $5 | $5 | $25 | 5x less | tools, images | frontier | | `claude-sonnet-5-5` | Anthropic | 200,000 | $0.5 | $2.5 | $2 | $10 | 4x less | tools, images | frontier | | `claude-sonnet-5` | Anthropic | 200,000 | $0.4 | $2 | $2 | $10 | 5x less | tools, images | frontier | | `claude-haiku-4-5-20251001` | Anthropic | 200,000 | $0.2 | $1 | $1 | $5 | 5x less | tools, images, starter balance | frontier | | `claude-fable-5-1` | Anthropic | 200,000 | $3.3 | $16.5 | $10 | $50 | 3x less | tools, images | frontier | | `gpt-5.5` | OpenAI | 1,000,000 | $1 | $6 | $5 | $30 | 5x less | tools, json, images, reasons first | frontier | | `gpt-6.1-sol` | OpenAI | 1,000,000 | $0.4 | $2 | $2 | $10 | 5x less | tools, json, images, reasons first | frontier | | `gpt-5` | OpenAI | 400,000 | $0.25 | $2 | $1.25 | $10 | 5x less | tools, json, images, reasons first | frontier | | `gemini-3.1-pro-preview` | Google | 1,048,576 | $0.4 | $2.4 | $2 | $12 | 5x less | tools, json, images, reasons first | frontier | | `gemini-3.5-flash` | Google | 1,048,576 | $0.3 | $1.8 | $1.5 | $9 | 5x less | tools, json, images, reasons first, starter balance | frontier | | `glm-5.3` | Z.ai | 1,000,000 | $0.77 | $2.42 | $1.4 | $4.4 | 1.8x less | tools, json, reasons first | open | | `qwen3.8-max` | Qwen (Alibaba) | 1,000,000 | $1.1 | $3.3 | $2 | $6 | 1.8x less | tools, json, reasons first | open | | `deepseek-v4.1-flash` | DeepSeek | 1,000,000 | $0.165 | $0.66 | $0.3 | $1.2 | 1.8x less | tools, reasons first | open | Which endpoint serves which model: | Route | Format | Key | Errors | Models | Cost | | --- | --- | --- | --- | --- | --- | | `POST /v1/chat/completions` | OpenAI chat completions | `Authorization: Bearer` or `x-api-key` | OpenAI | `claude-opus-5-5`, `claude-opus-5`, `claude-sonnet-5-5`, `claude-sonnet-5`, `claude-haiku-4-5-20251001`, `claude-fable-5-1`, `gpt-5.5`, `gpt-6.1-sol`, `gpt-5`, `gemini-3.1-pro-preview`, `gemini-3.5-flash`, `glm-5.3`, `qwen3.8-max`, `deepseek-v4.1-flash` | Billed per token | | `POST /v1/messages` | Anthropic Messages | `Authorization: Bearer` or `x-api-key` | Anthropic | `claude-opus-5-5`, `claude-opus-5`, `claude-sonnet-5-5`, `claude-sonnet-5`, `claude-haiku-4-5-20251001`, `claude-fable-5-1`, `glm-5.3`, `deepseek-v4.1-flash` | Billed per token | | `POST /v1/messages/count_tokens` | Anthropic Messages | `Authorization: Bearer` or `x-api-key` | Anthropic | `claude-opus-5-5`, `claude-opus-5`, `claude-sonnet-5-5`, `claude-sonnet-5`, `claude-haiku-4-5-20251001`, `claude-fable-5-1`, `glm-5.3`, `deepseek-v4.1-flash` | No charge | | `POST /v1/responses` | OpenAI Responses | `Authorization: Bearer` or `x-api-key` | OpenAI | `gpt-5.5`, `gpt-6.1-sol`, `gpt-5` | Billed per token | | `GET /v1/models` | OpenAI and Anthropic list | None | OpenAI | Every model | No charge | | `GET /v1/models/{id}` | One model | None | OpenAI | Every model | No charge | Check the official price on the maker's own page: Anthropic https://platform.claude.com/docs/en/about-claude/pricing, OpenAI https://openai.com/api/pricing/, Google https://ai.google.dev/gemini-api/docs/pricing. --- ## Contents **Getting started** — Quickstart **Connect your tools** — Claude Code, Codex CLI, OpenCode, OpenClaw, Hermes Agent, OpenAI SDK, Anthropic SDK **Core** — Supported endpoints, Authentication, Models, Chat completions, Streams, Tool calls, Structured output, Prompt cache, Reliability **Reference** — Prices and charges, Rate limits, Errors, Idempotency, Request ids, Limits and your data, Not supported yet, Changelog **Guides** — Move from OpenAI, Switch from another provider, Frameworks --- ## Quickstart Source: https://kontinentai.com/docs/quickstart Get a key, set one environment variable and send your first request to Claude, GPT or Gemini through the Kontinent AI API. Kontinent AI gives you Claude, GPT and Gemini models through one API at `https://api.kontinentai.com`. The API accepts three request formats: OpenAI chat completions, Anthropic Messages and OpenAI Responses. Thus, the OpenAI and Anthropic SDKs work with a new base URL and a new key. ### 1. Get an API key 1. [Sign in](/login) with Google or with a link that we send to your email. 2. Open [API keys](/dashboard/keys) in the dashboard. 3. Create a key. Copy it immediately. We show the key one time only. A key starts with `sk-kont-`. If you lose a key, revoke it and create a new key. ### 2. Set the key in your shell ```bash export KONTINENT_API_KEY="sk-kont-..." ``` The samples on this page and on the setup pages read the key from this variable. ### 3. Send a request **curl** ```bash curl https://api.kontinentai.com/v1/chat/completions \ -H "Authorization: Bearer $KONTINENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "claude-haiku-4-5-20251001", "messages": [{"role": "user", "content": "What is the capital of Kenya? One sentence."}] }' ``` **Python** ```python import os from openai import OpenAI client = OpenAI( api_key=os.environ["KONTINENT_API_KEY"], base_url="https://api.kontinentai.com/v1", ) response = client.chat.completions.create( model="claude-haiku-4-5-20251001", messages=[{"role": "user", "content": "What is the capital of Kenya? One sentence."}], ) print(response.choices[0].message.content) ``` **Node** ```javascript import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.KONTINENT_API_KEY, baseURL: "https://api.kontinentai.com/v1", }); const response = await client.chat.completions.create({ model: "claude-haiku-4-5-20251001", messages: [{ role: "user", content: "What is the capital of Kenya? One sentence." }], }); console.log(response.choices[0].message.content); ``` The Python sample needs `pip install openai`. The Node sample needs `npm install openai`. The response: ```json { "id": "chatcmpl-8f2a1c", "object": "chat.completion", "created": 1791331200, "model": "claude-haiku-4-5-20251001", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "The capital of Kenya is Nairobi." }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 21, "completion_tokens": 8, "total_tokens": 29 }, "kontinent": { "request_id": "req_8f2a1c", "cost_micros": 13, "cost_usd": 0.000013, "list_price_usd": 0.000061, "saved_usd": 0.000048 } } ``` The `kontinent` block shows the cost of the request in US dollars. `list_price_usd` is the cost of the same tokens at the official price of the maker. `saved_usd` is the difference. An OpenAI client ignores this block. ### The starter balance A new account gets a starter balance of $0.02. Until you add balance, you can use it on these models only: `claude-haiku-4-5-20251001` (/v1/messages, /v1/chat/completions); `gemini-3.5-flash` (/v1/chat/completions). On the starter balance, these limits also apply: - A request can write a maximum of 4,096 output tokens. - A prompt can have a maximum of 40,000 characters. - An account can send a maximum of 5 requests a minute. A request for a different model gets a `403` error with the code `STARTER_BALANCE_ONLY`. We do not charge for that request. In `GET /v1/models`, each starter model has `"starter": true`. > **Warning: Coding tools need more than the starter balance** > > Claude Code, Codex and OpenCode send prompts that are longer than 40,000 characters. Also, most of > their models are not starter models. Thus, these tools need balance from a top-up or a redemption code. ### Get more balance To get balance, top up on the [balance page](/dashboard/balance), in US dollars or in your own currency. A top-up starts at $1 by mobile money or bank transfer, and at $3 by card. You can also enter a redemption code there. The balance is in US dollars and it does not expire. ### Next steps - Connect a tool: [Claude Code](/docs/claude-code), [Codex](/docs/codex) or [OpenCode](/docs/opencode). - Use an SDK: [OpenAI SDK](/docs/openai-sdk) or [Anthropic SDK](/docs/anthropic-sdk). - Read which models each endpoint serves: [Supported endpoints](/docs/endpoints). - Compare our prices with the official prices: [Models](/docs/models). --- ## Claude Code Source: https://kontinentai.com/docs/claude-code Point Claude Code at the Kontinent AI Messages endpoint with two environment variables, choose a Claude model and make sure that the connection works. Claude Code sends Anthropic Messages requests. Kontinent AI serves that format at `https://api.kontinentai.com/v1/messages`, so Claude Code needs only a new base URL and a new key. Before you start, make sure that you have: - [Claude Code](https://code.claude.com/docs/en/setup), installed. - A Kontinent AI key. See [Quickstart](/docs/quickstart). - Balance from a top-up or a redemption code. The starter balance does not accept prompts of this size. ### 1. Set the variables Put these lines in your shell profile, for example `~/.zshrc` or `~/.bashrc`: ```bash export ANTHROPIC_BASE_URL="https://api.kontinentai.com" export ANTHROPIC_AUTH_TOKEN="sk-kont-..." export ANTHROPIC_MODEL="claude-sonnet-5-5" export ANTHROPIC_DEFAULT_OPUS_MODEL="claude-opus-5-5" export ANTHROPIC_DEFAULT_SONNET_MODEL="claude-sonnet-5-5" export ANTHROPIC_DEFAULT_HAIKU_MODEL="claude-haiku-4-5-20251001" ``` Do not add `/v1` to `ANTHROPIC_BASE_URL`. Claude Code adds `/v1/messages` to the base URL. | Variable | What it does | | --- | --- | | `ANTHROPIC_BASE_URL` | Sends the requests of Claude Code to Kontinent AI | | `ANTHROPIC_AUTH_TOKEN` | Your Kontinent AI key. Claude Code sends it in the `Authorization: Bearer` header | | `ANTHROPIC_MODEL` | The model for the session. You can change it with the `/model` command | | `ANTHROPIC_DEFAULT_OPUS_MODEL`, `ANTHROPIC_DEFAULT_SONNET_MODEL` | The models that the `opus` and `sonnet` names select | | `ANTHROPIC_DEFAULT_HAIKU_MODEL` | The model for background tasks, for example session titles. Set it to a model that we serve | > **Use ANTHROPIC_AUTH_TOKEN, not ANTHROPIC_API_KEY** > > Our API accepts the key in `Authorization: Bearer` and in `x-api-key`. Claude Code sends > `ANTHROPIC_API_KEY` in `x-api-key`, and it asks you to approve that key one time. With > `ANTHROPIC_AUTH_TOKEN`, Claude Code uses the key immediately. ### 2. Make sure that the connection works Send one small request with curl: ```bash curl -sS -w '\n%{http_code}\n' -X POST "$ANTHROPIC_BASE_URL/v1/messages" \ -H "Authorization: Bearer $ANTHROPIC_AUTH_TOKEN" \ -H "anthropic-version: 2023-06-01" \ -H "content-type: application/json" \ -d '{"model": "claude-haiku-4-5-20251001", "max_tokens": 16, "messages": [{"role": "user", "content": "Say OK."}]}' ``` A response that starts with `{"id":"msg_` and ends with `200` shows that the key and the URL are correct. A `401` shows that we did not accept the key. Then do these steps: 1. Start Claude Code from the same shell: `claude`. 2. Type `/status`. 3. Make sure that `Anthropic base URL` shows `https://api.kontinentai.com`. 4. Send a short prompt. Claude Code shows the answer. ### Use a settings file The variables in your shell apply only to programs that you start from that shell. To apply them everywhere that Claude Code runs, put them in the `env` block of `~/.claude/settings.json`: ```json { "env": { "ANTHROPIC_BASE_URL": "https://api.kontinentai.com", "ANTHROPIC_AUTH_TOKEN": "sk-kont-...", "ANTHROPIC_MODEL": "claude-sonnet-5-5", "ANTHROPIC_DEFAULT_OPUS_MODEL": "claude-opus-5-5", "ANTHROPIC_DEFAULT_SONNET_MODEL": "claude-sonnet-5-5", "ANTHROPIC_DEFAULT_HAIKU_MODEL": "claude-haiku-4-5-20251001" } } ``` > **Warning: Keep the key out of a shared file** > > Do not put the key in the `.claude/settings.json` file of a project. Other people can read that file > in your repository. ### Models You can use these Claude models with Claude Code. This list comes from our model catalogue: - `claude-opus-5-5` - `claude-opus-5` - `claude-sonnet-5-5` - `claude-sonnet-5` - `claude-haiku-4-5-20251001` - `claude-fable-5-1` - `glm-5.3` - `deepseek-v4.1-flash` To show these models in the `/model` list, set `CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY=1`. Claude Code then reads our `GET /v1/models` endpoint when it starts. ### Costs and limits - Claude Code streams its responses. A streamed Messages response does not include our `kontinent` cost block. We record the cost of each request, and the [dashboard](/dashboard) shows it. - Each request counts toward the limit of 60 requests a minute for each key. This includes the `count_tokens` requests that Claude Code sends. We do not charge for `count_tokens`. - A request that fails before the answer starts does not cost money. See [Reliability](/docs/reliability). ### Problems | Problem | What to do | | --- | --- | | `401` | Make sure that the key starts with `sk-kont-` and that you did not revoke it | | `402` | Your balance is too low. Top up on the balance page | | `403` with `STARTER_BALANCE_ONLY` | Your account has only the starter balance. Top up on the balance page | | `400` that names `context_management` or says `Extra inputs are not permitted` | Set `CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS=1`. This stops the test features of Claude Code | | `404` that names a model | Set the model variables above to models from the list on this page | | `429` | Too many requests. Wait for the time in the `Retry-After` header | The Claude Code documentation for gateways is at [code.claude.com](https://code.claude.com/docs/en/llm-gateway-connect). --- ## Codex CLI Source: https://kontinentai.com/docs/codex Add Kontinent AI to the Codex CLI as a model provider in config.toml. Choose a GPT model and make sure that the connection works. The Codex CLI sends OpenAI Responses requests. Kontinent AI serves that format at `https://api.kontinentai.com/v1/responses`. You add Kontinent AI to Codex as a model provider. Before you start, make sure that you have: - The [Codex CLI](https://developers.openai.com/codex), installed. - A Kontinent AI key. See [Quickstart](/docs/quickstart). - Balance from a top-up or a redemption code. The GPT models are not starter models. ### 1. Set the key ```bash export KONTINENT_API_KEY="sk-kont-..." ``` Put this line in your shell profile, for example `~/.zshrc`, so that each new shell has the key. ### 2. Add the provider Open `~/.codex/config.toml`. Add these lines: ```toml model = "gpt-5.5" model_provider = "kontinent" [model_providers.kontinent] name = "Kontinent AI" base_url = "https://api.kontinentai.com/v1" env_key = "KONTINENT_API_KEY" wire_api = "responses" ``` | Key | What it does | | --- | --- | | `model` | The model that Codex uses. Select one from the list on this page | | `model_provider` | Selects the provider table below it | | `base_url` | Our API. Codex adds `/responses` to it | | `env_key` | The name of the environment variable that holds your key. Codex sends the key in `Authorization: Bearer` | | `wire_api` | The request format. `responses` is the only value that Codex supports | ### 3. Make sure that the connection works Send one small request with curl: ```bash curl -sS -w '\n%{http_code}\n' https://api.kontinentai.com/v1/responses \ -H "Authorization: Bearer $KONTINENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model": "gpt-5.5", "input": "Say OK.", "max_output_tokens": 256}' ``` A JSON response that ends with `200` shows that the key and the URL are correct. Then run Codex: ```bash codex exec "Say OK." ``` Codex prints the answer of the model. ### Models These models work on the Responses endpoint. This list comes from our model catalogue: - `gpt-5.5` - `gpt-6.1-sol` - `gpt-5` To use a different model for one session, run `codex --model gpt-5`. > **Claude and Gemini do not work in Codex** > > We serve Claude on the Messages endpoint and on the chat completions endpoint, and Gemini on the > chat completions endpoint only. Codex uses the Responses endpoint, so it can use only the models in > the list above. For Claude, use [Claude Code](/docs/claude-code). For Gemini, use > [OpenCode](/docs/opencode). ### Costs and limits - Codex streams its responses. A streamed Responses response does not include our `kontinent` cost block. We record the cost of each request, and the [dashboard](/dashboard) shows it. - The GPT models think before they answer, and we charge for that output as output tokens. - Each request counts toward the limit of 60 requests a minute for each key. ### Problems | Problem | What to do | | --- | --- | | `401` | Make sure that the shell that starts Codex has `KONTINENT_API_KEY` | | `402` | Your balance is too low. Top up on the balance page | | `400` that says `is not served on /v1/responses` | Select a model from the list on this page | | `404` | Make sure that `base_url` ends with `/v1` and not with `/v1/responses` | The Codex documentation for model providers is at [learn.chatgpt.com](https://learn.chatgpt.com/docs/config-file/config-advanced). --- ## OpenCode Source: https://kontinentai.com/docs/opencode Add Kontinent AI to OpenCode as a custom provider in opencode.json. Use Claude, GPT and Gemini models through the chat completions endpoint. OpenCode can send OpenAI chat completions requests to a custom provider. Kontinent AI serves that format at `https://api.kontinentai.com/v1/chat/completions`, for Claude, GPT and Gemini models. Before you start, make sure that you have: - [OpenCode](https://opencode.ai/docs/), installed. - A Kontinent AI key. See [Quickstart](/docs/quickstart). - Balance from a top-up or a redemption code. OpenCode sends prompts that are too long for the starter balance. ### 1. Set the key ```bash export KONTINENT_API_KEY="sk-kont-..." ``` Put this line in your shell profile, for example `~/.zshrc`, so that each new shell has the key. ### 2. Add the provider Open `~/.config/opencode/opencode.json`. If the file does not exist, create it. Add the `kontinent` provider: ```json { "$schema": "https://opencode.ai/config.json", "provider": { "kontinent": { "npm": "@ai-sdk/openai-compatible", "name": "Kontinent AI", "options": { "baseURL": "https://api.kontinentai.com/v1", "apiKey": "{env:KONTINENT_API_KEY}" }, "models": { "claude-sonnet-5-5": { "name": "Claude Sonnet 5.5" }, "claude-opus-5-5": { "name": "Claude Opus 5.5" }, "claude-haiku-4-5-20251001": { "name": "Claude Haiku 4.5" }, "gpt-5.5": { "name": "GPT-5.5" }, "gemini-3.1-pro-preview": { "name": "Gemini 3.1 Pro" }, "gemini-3.5-flash": { "name": "Gemini 3.5 Flash" } } } }, "model": "kontinent/claude-sonnet-5-5" } ``` | Key | What it does | | --- | --- | | `npm` | The package that OpenCode uses to send chat completions requests | | `options.baseURL` | Our API. The package adds `/chat/completions` to it | | `options.apiKey` | `{env:KONTINENT_API_KEY}` reads the key from your environment | | `models` | The model ids that OpenCode shows. Each key must be a model id from the list on this page | | `model` | The model that OpenCode selects when it starts, as `provider/model` | To put the provider in one project only, put the same content in `opencode.json` at the root of the project. Do not put the key itself in that file. ### 3. Make sure that the connection works Send one small request with curl: ```bash curl -sS -w '\n%{http_code}\n' https://api.kontinentai.com/v1/chat/completions \ -H "Authorization: Bearer $KONTINENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model": "claude-haiku-4-5-20251001", "max_tokens": 16, "messages": [{"role": "user", "content": "Say OK."}]}' ``` A JSON response that ends with `200` shows that the key and the URL are correct. Then do these steps: 1. Run `opencode models kontinent`. OpenCode shows the models from your file. 2. Run `opencode run -m kontinent/claude-haiku-4-5-20251001 "Say OK."`. OpenCode prints the answer. ### Models These models work on the chat completions endpoint. This list comes from our model catalogue. You can add each of them to the `models` object: - `claude-opus-5-5` - `claude-opus-5` - `claude-sonnet-5-5` - `claude-sonnet-5` - `claude-haiku-4-5-20251001` - `claude-fable-5-1` - `gpt-5.5` - `gpt-6.1-sol` - `gpt-5` - `gemini-3.1-pro-preview` - `gemini-3.5-flash` - `glm-5.3` - `qwen3.8-max` - `deepseek-v4.1-flash` ### Costs and limits - OpenCode streams its responses. We record the cost of each request, and the [dashboard](/dashboard) shows it. - Each request counts toward the limit of 60 requests a minute for each key. - The file above has no `limit` object for each model. If you add one, OpenCode needs `context` and `output`. The `context_window` field of `GET /v1/models` gives the context window of each model. ### Problems | Problem | What to do | | --- | --- | | `401` | Make sure that the shell that starts OpenCode has `KONTINENT_API_KEY` | | `402` | Your balance is too low. Top up on the balance page | | `400` that says `is not served on /v1/chat/completions` | Remove that model from the `models` object | | `404` that names a model | Make sure that each key in `models` is a model id from the list on this page | The OpenCode documentation for custom providers is at [opencode.ai](https://opencode.ai/docs/providers/). --- ## OpenClaw Source: https://kontinentai.com/docs/openclaw Add Kontinent AI to OpenClaw as a custom provider in openclaw.json. Use Claude, GPT and Gemini models through the chat completions endpoint. OpenClaw can send OpenAI chat completions requests to a custom provider. Kontinent AI serves that format at `https://api.kontinentai.com/v1/chat/completions`, for Claude, GPT and Gemini models. Before you start, make sure that you have: - [OpenClaw](https://docs.openclaw.ai/start/getting-started), installed. - A Kontinent AI key. See [Quickstart](/docs/quickstart). - Balance from a top-up or a redemption code. OpenClaw sends prompts that are too long for the starter balance. ### 1. Set the key Put the key in `~/.openclaw/.env`. If the file does not exist, create it: ```bash KONTINENT_API_KEY=sk-kont-... ``` OpenClaw reads this file when the Gateway starts. The Gateway can run as a background service, so a key in your shell profile is not enough. ### 2. Add the provider Open `~/.openclaw/openclaw.json`. If the file does not exist, create it. Add the `kontinent` provider and set the default model: ```json5 { models: { mode: "merge", providers: { kontinent: { baseUrl: "https://api.kontinentai.com/v1", apiKey: "${KONTINENT_API_KEY}", api: "openai-completions", models: [ { id: "claude-sonnet-5-5", name: "Claude Sonnet 5.5", input: ["text", "image"], contextWindow: 200000, maxTokens: 16000 }, { id: "claude-opus-5-5", name: "Claude Opus 5.5", input: ["text", "image"], contextWindow: 200000, maxTokens: 16000 }, { id: "claude-haiku-4-5-20251001", name: "Claude Haiku 4.5", input: ["text", "image"], contextWindow: 200000, maxTokens: 16000 }, { id: "gpt-5.5", name: "GPT-5.5", input: ["text", "image"], contextWindow: 1000000, maxTokens: 16000 }, { id: "gemini-3.1-pro-preview", name: "Gemini 3.1 Pro", input: ["text", "image"], contextWindow: 1048576, maxTokens: 16000 }, ], }, }, }, agents: { defaults: { model: { primary: "kontinent/claude-sonnet-5-5" }, }, }, } ``` If the file has other content, add the `models` and `agents` keys to it. Do not replace the other keys. | Key | What it does | | --- | --- | | `baseUrl` | Our API. OpenClaw adds `/chat/completions` to it | | `apiKey` | `${KONTINENT_API_KEY}` reads the key from the environment | | `api` | `openai-completions` sends OpenAI chat completions requests | | `models` | The models that OpenClaw can use. Each `id` must be a model id from the list on this page | | `input` | `["text", "image"]` lets OpenClaw send images. All the models on this page accept images | | `contextWindow` | The context window of the model. The `context_window` field of `GET /v1/models` gives this value | | `maxTokens` | The output limit for each request. Without it, the limit is 16,000 tokens, our default | | `agents.defaults.model.primary` | The model that OpenClaw uses, as `provider/model` | You can also edit the configuration with `openclaw config set`. Run `openclaw config validate` after you edit the file. OpenClaw refuses a configuration that has an unknown key. ### 3. Make sure that the connection works Send one small request with curl: ```bash export KONTINENT_API_KEY="sk-kont-..." curl -sS -w '\n%{http_code}\n' https://api.kontinentai.com/v1/chat/completions \ -H "Authorization: Bearer $KONTINENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model": "claude-haiku-4-5-20251001", "max_tokens": 16, "messages": [{"role": "user", "content": "Say OK."}]}' ``` A JSON response that ends with `200` shows that the key and the URL are correct. Then do these steps: 1. Run `openclaw models list --provider kontinent`. OpenClaw shows the models from your file. 2. Run `openclaw models status`. The default model is `kontinent/claude-sonnet-5-5`. 3. Run `openclaw agent exec "Say OK." --model kontinent/claude-haiku-4-5-20251001`. OpenClaw prints the answer. ### Models These models work on the chat completions endpoint. This list comes from our model catalogue. You can add each of them to the `models` array: - `claude-opus-5-5` - `claude-opus-5` - `claude-sonnet-5-5` - `claude-sonnet-5` - `claude-haiku-4-5-20251001` - `claude-fable-5-1` - `gpt-5.5` - `gpt-6.1-sol` - `gpt-5` - `gemini-3.1-pro-preview` - `gemini-3.5-flash` - `glm-5.3` - `qwen3.8-max` - `deepseek-v4.1-flash` All of these models accept tools, and an agent needs tools. For most agent work, use `claude-sonnet-5-5`. For long and difficult tasks, use `claude-opus-5-5`, `gpt-5.5` or `gemini-3.1-pro-preview`. For quick tasks at a lower cost, use `claude-haiku-4-5-20251001`. ### Costs and limits - OpenClaw streams its responses. We record the cost of each request, and the [dashboard](/dashboard) shows it. - OpenClaw can send requests without a message from you, for example from scheduled jobs. Each request costs money. - Each request counts toward the limit of 60 requests a minute for each key. ### Problems | Problem | What to do | | --- | --- | | `401` | Make sure that `~/.openclaw/.env` has `KONTINENT_API_KEY`. Then restart the Gateway | | `402` | Your balance is too low. Top up on the balance page | | `400` that says `is not served on /v1/chat/completions` | Remove that model from the `models` array | | `404` that names a model | Make sure that each `id` in `models` is a model id from the list on this page | The OpenClaw documentation for custom providers is at [docs.openclaw.ai](https://docs.openclaw.ai/gateway/config-tools/custom-providers). --- ## Hermes Agent Source: https://kontinentai.com/docs/hermes-agent Add Kontinent AI to Hermes Agent by Nous Research as a custom provider in config.yaml. Use Claude, GPT and Gemini models through the chat completions endpoint. Hermes Agent, by Nous Research, can send OpenAI chat completions requests to a custom provider. Kontinent AI serves that format at `https://api.kontinentai.com/v1/chat/completions`, for Claude, GPT and Gemini models. Before you start, make sure that you have: - [Hermes Agent](https://hermes-agent.nousresearch.com/docs/getting-started/quickstart), installed. - A Kontinent AI key. See [Quickstart](/docs/quickstart). - Balance from a top-up or a redemption code. Hermes Agent sends prompts that are too long for the starter balance. ### 1. Set the key Put the key in `~/.hermes/.env`: ```bash KONTINENT_API_KEY=sk-kont-... ``` You can also run `hermes config set KONTINENT_API_KEY sk-kont-...`. Hermes Agent writes a name in capital letters to `~/.hermes/.env`. ### 2. Add the provider Open `~/.hermes/config.yaml`. Add the `kontinent` provider and set the model: ```yaml providers: kontinent: api: https://api.kontinentai.com/v1 key_env: KONTINENT_API_KEY transport: chat_completions context_length: 200000 extra_body: max_tokens: 16000 model: default: claude-sonnet-5-5 provider: custom:kontinent ``` If the file already has a `providers` or a `model` key, change that key. Do not add a second one. | Key | What it does | | --- | --- | | `api` | Our API. Hermes Agent adds `/chat/completions` to it | | `key_env` | The name of the variable that holds the key | | `transport` | `chat_completions` sends OpenAI chat completions requests | | `context_length` | The context window. 200,000 tokens is the smallest window of the models on this page | | `extra_body.max_tokens` | The output limit for each request. Without it, the limit is 16,000 tokens, our default | | `model.default` | The model that Hermes Agent uses. It must be a model id from the list on this page | | `model.provider` | `custom:kontinent` selects the provider above | To use a different model in a session, type `/model custom:kontinent:gpt-5.5`. ### 3. Make sure that the connection works Send one small request with curl: ```bash export KONTINENT_API_KEY="sk-kont-..." curl -sS -w '\n%{http_code}\n' https://api.kontinentai.com/v1/chat/completions \ -H "Authorization: Bearer $KONTINENT_API_KEY" \ -H "Content-Type: application/json" \ -d '{"model": "claude-haiku-4-5-20251001", "max_tokens": 16, "messages": [{"role": "user", "content": "Say OK."}]}' ``` A JSON response that ends with `200` shows that the key and the URL are correct. Then do these steps: 1. Run `hermes doctor`. Hermes Agent shows the problems in the configuration, if there are problems. 2. Run `hermes chat --oneshot -q "Say OK."`. Hermes Agent prints the answer. 3. Run `hermes`. The banner shows the model and the provider. ### Models These models work on the chat completions endpoint. This list comes from our model catalogue. You can use each of them as `model.default`: - `claude-opus-5-5` - `claude-opus-5` - `claude-sonnet-5-5` - `claude-sonnet-5` - `claude-haiku-4-5-20251001` - `claude-fable-5-1` - `gpt-5.5` - `gpt-6.1-sol` - `gpt-5` - `gemini-3.1-pro-preview` - `gemini-3.5-flash` - `glm-5.3` - `qwen3.8-max` - `deepseek-v4.1-flash` All of these models accept tools, and an agent needs tools. Hermes Agent also needs a context window of 64,000 tokens or more, and all of these models have it. For most agent work, use `claude-sonnet-5-5`. For long and difficult tasks, use `claude-opus-5-5`, `gpt-5.5` or `gemini-3.1-pro-preview`. For quick tasks at a lower cost, use `claude-haiku-4-5-20251001`. ### Costs and limits - Hermes Agent uses the same model for its background tasks, for example session titles and summaries. Each of these requests costs money. We record the cost of each request, and the [dashboard](/dashboard) shows it. - Each request counts toward the limit of 60 requests a minute for each key. ### Problems | Problem | What to do | | --- | --- | | `401` | Make sure that `~/.hermes/.env` has `KONTINENT_API_KEY` | | `402` | Your balance is too low. Top up on the balance page | | `400` that says `is not served on /v1/chat/completions` | Set `model.default` to a model from the list on this page | | `404` that names a model | Make sure that `model.default` is a model id from the list on this page | The Hermes Agent documentation for custom providers is at [hermes-agent.nousresearch.com](https://hermes-agent.nousresearch.com/docs/integrations/providers). --- ## OpenAI SDK Source: https://kontinentai.com/docs/openai-sdk Set the base URL and the key in the OpenAI Python or Node SDK. Then use chat completions for Claude, GPT and Gemini, and Responses for GPT. The OpenAI SDKs work with Kontinent AI. Change two settings: the base URL and the key. Keep the rest of your code. ### 1. Install the SDK **Python** ```bash pip install openai ``` **Node** ```bash npm install openai ``` ### 2. Set the base URL and the key Use `https://api.kontinentai.com/v1` as the base URL. Use your Kontinent AI key as the API key. **Python** ```python import os from openai import OpenAI client = OpenAI( api_key=os.environ["KONTINENT_API_KEY"], base_url="https://api.kontinentai.com/v1", ) ``` **Node** ```javascript import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.KONTINENT_API_KEY, baseURL: "https://api.kontinentai.com/v1", }); ``` The SDKs also read two environment variables. If you set them, you can call `OpenAI()` with no arguments: ```bash export OPENAI_BASE_URL="https://api.kontinentai.com/v1" export OPENAI_API_KEY="$KONTINENT_API_KEY" ``` ### 3. Send a chat completion **Python** ```python reply = client.chat.completions.create( model="gemini-3.5-flash", messages=[{"role": "user", "content": "Name three long rivers."}], ) print(reply.choices[0].message.content) print(reply.model_extra["kontinent"]) ``` **Node** ```javascript const reply = await client.chat.completions.create({ model: "gemini-3.5-flash", messages: [{ role: "user", content: "Name three long rivers." }], }); console.log(reply.choices[0].message.content); console.log(reply.kontinent); ``` If the program prints an answer and a `kontinent` block with `cost_usd`, the connection works. These models work on `/v1/chat/completions`: - `claude-opus-5-5` - `claude-opus-5` - `claude-sonnet-5-5` - `claude-sonnet-5` - `claude-haiku-4-5-20251001` - `claude-fable-5-1` - `gpt-5.5` - `gpt-6.1-sol` - `gpt-5` - `gemini-3.1-pro-preview` - `gemini-3.5-flash` - `glm-5.3` - `qwen3.8-max` - `deepseek-v4.1-flash` ### Send a Responses request The Responses endpoint serves the GPT models only. **Python** ```python response = client.responses.create( model="gpt-5.5", input="Name three long rivers.", ) print(response.output_text) ``` **Node** ```javascript const response = await client.responses.create({ model: "gpt-5.5", input: "Name three long rivers.", }); console.log(response.output_text); ``` These models work on `/v1/responses`: - `gpt-5.5` - `gpt-6.1-sol` - `gpt-5` > **One model id, more than one endpoint** > > A model can work on more than one endpoint. `gpt-5.5` works on chat completions and on Responses. > `claude-sonnet-5-5` does not work on Responses. A request to an endpoint that does not serve the > model gets a `400` error. The error message names the endpoint to use. ### Retries The SDKs retry `429` and `5xx` errors with backoff. They do not retry `402`, because a low balance does not change when you send the request again. To change the number of retries, set `max_retries` in Python or `maxRetries` in Node. ### Next steps - [Streams](/docs/streaming), [Tool calls](/docs/tool-calling) and [Structured output](/docs/structured-output) for chat completions. - [Errors](/docs/errors) for the error codes. --- ## Anthropic SDK Source: https://kontinentai.com/docs/anthropic-sdk Set the base URL and the key in the Anthropic Python or Node SDK, and send Messages requests to Claude models through Kontinent AI. The Anthropic SDKs send Messages requests. Kontinent AI serves that format at `https://api.kontinentai.com/v1/messages`. Change two settings: the base URL and the key. ### 1. Install the SDK **Python** ```bash pip install anthropic ``` **Node** ```bash npm install @anthropic-ai/sdk ``` ### 2. Set the base URL and the key Use `https://api.kontinentai.com` as the base URL, with no `/v1`. The SDK adds `/v1/messages`. **Python** ```python import os from anthropic import Anthropic client = Anthropic( api_key=os.environ["KONTINENT_API_KEY"], base_url="https://api.kontinentai.com", ) ``` **Node** ```javascript import Anthropic from "@anthropic-ai/sdk"; const client = new Anthropic({ apiKey: process.env.KONTINENT_API_KEY, baseURL: "https://api.kontinentai.com", }); ``` The SDK sends the key in the `x-api-key` header. Our API accepts it there. The SDKs also read two environment variables. If you set them, you can call `Anthropic()` with no arguments: ```bash export ANTHROPIC_BASE_URL="https://api.kontinentai.com" export ANTHROPIC_API_KEY="$KONTINENT_API_KEY" ``` ### 3. Send a message **Python** ```python message = client.messages.create( model="claude-haiku-4-5-20251001", max_tokens=256, messages=[{"role": "user", "content": "Name three long rivers."}], ) print(message.content[0].text) print(message.model_extra["kontinent"]) ``` **Node** ```javascript const message = await client.messages.create({ model: "claude-haiku-4-5-20251001", max_tokens: 256, messages: [{ role: "user", content: "Name three long rivers." }], }); console.log(message.content[0].text); console.log(message.kontinent); ``` If the program prints an answer and a `kontinent` block with `cost_usd`, the connection works. ### Models These models work on `/v1/messages`. This list comes from our model catalogue: - `claude-opus-5-5` - `claude-opus-5` - `claude-sonnet-5-5` - `claude-sonnet-5` - `claude-haiku-4-5-20251001` - `claude-fable-5-1` - `glm-5.3` - `deepseek-v4.1-flash` ### What we send to the model We send your request body to the model as you wrote it. We change only the `model` field, and on the starter balance, the `max_tokens` field. We also send the `anthropic-version` and `anthropic-beta` headers. - **Prompt cache.** `cache_control` markers go to the model. A cache read costs the cached input price, and a cache write costs the cache write price. See [Prompt cache](/docs/prompt-caching). - **Token counts.** `client.messages.count_tokens()` works. We do not charge for it. If the model cannot count the tokens, we send an estimate, and the response has the header `x-kontinent-estimated: true`. - **Streams.** `client.messages.stream()` works. A streamed response does not include our `kontinent` block. We record the cost of each request, and the [dashboard](/dashboard) shows it. > **Errors use the Anthropic format** > > An error from `/v1/messages` has the Anthropic shape: `{"type": "error", "error": {"type", "message"}}`. > Thus, the error classes of the Anthropic SDK work as usual. A low balance is a `402` with the type > `billing_error`. See [Errors](/docs/errors). --- ## Supported endpoints Source: https://kontinentai.com/docs/endpoints The five API routes and the models that each route serves. Also the key headers, the two error formats, idempotency and the cost block. The API accepts three request formats on one host. Each model works only in the formats that this table shows. We make the table from our model catalogue each time that you open this page. | Route | Format | Key | Errors | Models | Cost | | --- | --- | --- | --- | --- | --- | | `POST /v1/chat/completions` | OpenAI chat completions | `Authorization: Bearer` or `x-api-key` | OpenAI | `claude-opus-5-5`, `claude-opus-5`, `claude-sonnet-5-5`, `claude-sonnet-5`, `claude-haiku-4-5-20251001`, `claude-fable-5-1`, `gpt-5.5`, `gpt-6.1-sol`, `gpt-5`, `gemini-3.1-pro-preview`, `gemini-3.5-flash`, `glm-5.3`, `qwen3.8-max`, `deepseek-v4.1-flash` | Billed per token | | `POST /v1/messages` | Anthropic Messages | `Authorization: Bearer` or `x-api-key` | Anthropic | `claude-opus-5-5`, `claude-opus-5`, `claude-sonnet-5-5`, `claude-sonnet-5`, `claude-haiku-4-5-20251001`, `claude-fable-5-1`, `glm-5.3`, `deepseek-v4.1-flash` | Billed per token | | `POST /v1/messages/count_tokens` | Anthropic Messages | `Authorization: Bearer` or `x-api-key` | Anthropic | `claude-opus-5-5`, `claude-opus-5`, `claude-sonnet-5-5`, `claude-sonnet-5`, `claude-haiku-4-5-20251001`, `claude-fable-5-1`, `glm-5.3`, `deepseek-v4.1-flash` | No charge | | `POST /v1/responses` | OpenAI Responses | `Authorization: Bearer` or `x-api-key` | OpenAI | `gpt-5.5`, `gpt-6.1-sol`, `gpt-5` | Billed per token | | `GET /v1/models` | OpenAI and Anthropic list | None | OpenAI | Every model | No charge | | `GET /v1/models/{id}` | One model | None | OpenAI | Every model | No charge | A request for a model on an endpoint that does not serve it gets a `400` error before we charge money. The error message names the endpoint to use: ```json { "error": { "message": "Model \"claude-sonnet-5-5\" is not served on /v1/responses. Use /v1/messages or /v1/chat/completions.", "type": "invalid_request_error", "param": "model", "code": "unsupported_endpoint", "details": { "served_on": ["/v1/messages", "/v1/chat/completions"] } } } ``` ### Base URL | Client | Base URL | | --- | --- | | OpenAI SDKs, OpenCode, Codex | `https://api.kontinentai.com/v1` | | Anthropic SDKs, Claude Code | `https://api.kontinentai.com` | The OpenAI clients add `/chat/completions` or `/responses` to the base URL. The Anthropic clients add `/v1/messages`. ### Key headers Each endpoint that costs money accepts the key in one of two headers: ```http Authorization: Bearer sk-kont-... x-api-key: sk-kont-... ``` The OpenAI SDKs send the first header. The Anthropic SDKs send the second header. Claude Code sends the first header with `ANTHROPIC_AUTH_TOKEN` and the second header with `ANTHROPIC_API_KEY`. `GET /v1/models` needs no key. See [Authentication](/docs/authentication). ### Request bodies - **`/v1/chat/completions`** reads the body and makes a new request from it. It accepts only the parameters on [Chat completions](/docs/chat-completions). It refuses a different parameter with a `400` error that names the parameter. - **`/v1/messages` and `/v1/responses`** send your body to the model as you wrote it. We change only `model`. On the starter balance, we also lower `max_tokens` or `max_output_tokens` to 4,096. The model makes the decision about the other fields. `/v1/messages` needs `model`, `max_tokens` and `messages`. `/v1/messages` also sends your `anthropic-version` and `anthropic-beta` headers to the model. ### Errors The error format agrees with the format of the endpoint, so that the error classes of each SDK work. `/v1/chat/completions`, `/v1/responses` and `/v1/models` use the OpenAI format: ```json { "error": { "message": "Insufficient balance: $0.001230 required, $0.000400 available", "type": "insufficient_quota", "param": null, "code": "INSUFFICIENT_BALANCE" } } ``` `/v1/messages` and `/v1/messages/count_tokens` use the Anthropic format: ```json { "type": "error", "error": { "type": "billing_error", "message": "Insufficient balance: $0.001230 required, $0.000400 available" }, "request_id": "req_8f2a1c" } ``` The HTTP status is the same in the two formats. For the list of statuses, see [Errors](/docs/errors). ### Idempotency Send an `Idempotency-Key` header to make a retry safe. The rules are the same on `/v1/chat/completions`, `/v1/messages` and `/v1/responses`: - The header works only on a request without `stream: true`. - A request with the header and `stream: true` gets a `400` error with the code `idempotency_unsupported_for_stream`. - A repeat of a completed request gets the first response again, with the header `Idempotent-Replay: true`. We do not charge for the repeat. See [Idempotency](/docs/idempotency). ### The cost block A response without `stream: true` has a `kontinent` block. The block is at the top level of the JSON, so the SDKs ignore it: ```json "kontinent": { "request_id": "req_8f2a1c", "cost_micros": 13, "cost_usd": 0.000013, "list_price_usd": 0.000061, "saved_usd": 0.000048 } ``` | Field | What it is | | --- | --- | | `request_id` | The id of the request. The `x-request-id` header has the same value | | `cost_micros` | The cost of the request in micro-dollars. One micro-dollar is $0.000001. This is the amount that we take from your balance | | `cost_usd` | The same cost in US dollars | | `list_price_usd` | The cost of the same tokens at the official price of the maker | | `saved_usd` | `list_price_usd` minus `cost_usd` | > **Streamed responses** > > A streamed chat completion has the block on its usage frame, if you send > `stream_options: {"include_usage": true}`. See [Streams](/docs/streaming). > > A streamed Messages response and a streamed Responses response do not have the block. These formats > have no field for it. We record the cost of each request, and the [dashboard](/dashboard) shows it. --- ## Authentication Source: https://kontinentai.com/docs/authentication The two headers that carry an API key and the format of a key. What a key can do, and what to do when a key leaks. Each request to an endpoint that costs money carries your API key in one of two headers: ```http Authorization: Bearer sk-kont-... x-api-key: sk-kont-... ``` The two headers have the same effect. The OpenAI SDKs, Codex and OpenCode send the first header. The Anthropic SDKs send the second header. Claude Code sends the header that agrees with its variable: `ANTHROPIC_AUTH_TOKEN` sends the first header, and `ANTHROPIC_API_KEY` sends the second header. A key starts with `sk-kont-`, and 48 hex characters follow. The `sk-` prefix tells the API that the value is a key. The `kont-` part lets a secret scanner find a key that you put in a public place by accident. ### Create a key Create a key on the [API keys page](/dashboard/keys) of the dashboard. We show the key one time only. We keep only a hash of the key, so we cannot show it to you again. If you lose a key, revoke it and create a new key. > **Warning: Keep the key on a server** > > Do not put a key in a web page or in a mobile app. A person can read the key from the device. The > `/v1` endpoints send no CORS headers, so a browser cannot call them. ### A key cannot manage keys Only a signed-in dashboard session can create, list or revoke keys. A key can spend your balance, but it cannot make a new key. Thus, if a key leaks, revoke it. The person who has the key cannot make a replacement. ### Revoke a key Revoke a key on the [API keys page](/dashboard/keys). The key stops on the next request. We remove the key from our cache at the same time. ### What each endpoint needs | Endpoint | Key | Notes | | --- | --- | --- | | `POST /v1/chat/completions` | Yes | The dashboard playground uses a session cookie instead, with a lower limit | | `POST /v1/messages` | Yes | | | `POST /v1/messages/count_tokens` | Yes | No charge | | `POST /v1/responses` | Yes | | | `GET /v1/models`, `GET /v1/models/{id}` | No | The price list is public | | `/api/v1/*` | Session cookie | The dashboard uses these routes. Do not use them in your code | ### Errors A request with no key, a key in the wrong format or a revoked key gets a `401` error with the type `authentication_error`. On `/v1/messages`, the error has the Anthropic format. See [Errors](/docs/errors). A correct key on an account with too little balance gets a `402` error, not a `401` error. A `401` means that you must correct the key. A `402` means that you must add balance. --- ## Models Source: https://kontinentai.com/docs/models The full catalogue with our prices beside the official prices, context windows and formats. We make it from the same registry that the API uses. Put a model id in the `model` field of a request. `GET /v1/models` gives the same list and needs no key. We make this table from our model catalogue each time that you open the page. See "The catalogue" above for the full table of models, prices and capabilities. The table shows the frontier models first. The open models (Kimi, GLM, Qwen and MiniMax) come after them. Each open model has its own multiple, and it is smaller than the frontier multiple. To check an official price, open the price page of the maker: [Anthropic](https://platform.claude.com/docs/en/about-claude/pricing), [OpenAI](https://openai.com/api/pricing/), [Google](https://ai.google.dev/gemini-api/docs/pricing), [Moonshot AI](https://platform.kimi.ai/docs/pricing/chat), [Z.ai](https://docs.z.ai/guides/overview/pricing), [Qwen](https://www.alibabacloud.com/help/en/model-studio/model-pricing) and [MiniMax](https://platform.minimax.io/docs/guides/pricing-paygo). ### What `GET /v1/models` returns The response has the OpenAI list format and the Anthropic list format. This is one entry: ```json { "id": "claude-haiku-4-5-20251001", "object": "model", "type": "model", "display_name": "claude-haiku-4-5-20251001", "owned_by": "kontinent", "maker": "anthropic", "formats": ["anthropic", "openai"], "endpoints": ["/v1/messages", "/v1/chat/completions"], "input_modalities": ["text", "image"], "context_window": 200000, "reasoning": false, "starter": true, "supported_parameters": ["tools", "vision"], "performance": null, "pricing": { "unit": "token", "usd_per_million_input": 0.2, "usd_per_million_output": 1, "usd_per_million_cached_input": 0.02, "usd_per_million_cache_write": 0.25, "multiple_of_list": 0.2, "list": { "usd_per_million_input": 1, "usd_per_million_output": 5, "usd_per_million_cached_input": 0.1, "usd_per_million_cache_write": 1.25 } } } ``` | Field | What it is | | --- | --- | | `formats`, `endpoints` | The endpoints that serve the model. See [Supported endpoints](/docs/endpoints) | | `input_modalities` | What the model can read: `["text"]` or `["text", "image"]` | | `context_window` | The maximum number of tokens in one request, from the maker | | `reasoning` | `true` if the model thinks before it answers by default. See [Models that think first](#models-that-think-first) | | `starter` | `true` if the starter balance works on this model | | `supported_parameters` | Capabilities on `/v1/chat/completions`: `tools`, `structured-output` and `vision` | | `performance` | Time to the first token and tokens per second, from our nightly test. `null` before the first test | | `pricing` | Our price in US dollars for each million tokens, the official price (`pricing.list`) and the multiple of the official price | The JSON above is a copy. The live endpoint is always correct. ### Capabilities `supported_parameters` controls `/v1/chat/completions`. If you send `tools`, `response_format` or an image to a model that does not show that capability, you get a `400` error that names the parameter. We do not charge for that request. > **We will test each capability** > > The capabilities in the catalogue are the capabilities that we expect to work through our supply. We > have not yet tested each one through each route. Claude models do not show `structured-output`, > because we do not know yet if `response_format` works for them on the chat completions format. > The open models show only the capabilities that their makers document. On `/v1/messages` and `/v1/responses`, we send your body to the model as you wrote it. The model makes the decision about each field. ### Models that think first Some models think before they answer. We charge for that output as output tokens, but the response does not show it. In `GET /v1/models`, these models have `"reasoning": true`. - On a short answer, the thought tokens can be most of the cost. - A small `max_tokens` value can stop the model before it writes the answer. Then you get an empty answer and a charge for the thought tokens. On `/v1/chat/completions`, you can send `reasoning_effort` to a GPT or Gemini model. We send it to the model without a change: **Python** ```python client.chat.completions.create( model="gemini-3.5-flash", messages=[{"role": "user", "content": "What is 2+2?"}], reasoning_effort="low", ) ``` Claude models think only when the request asks. On `/v1/messages`, send the `thinking` field of the Anthropic format. ### Get one model **curl** ```bash curl https://api.kontinentai.com/v1/models/claude-haiku-4-5-20251001 ``` **Python** ```python client.models.retrieve("claude-haiku-4-5-20251001") ``` An unknown id gets a `404` error. We never send your request to a different model. --- ## Chat completions Source: https://kontinentai.com/docs/chat-completions The OpenAI chat completions endpoint for Claude, GPT and Gemini. The parameters that it accepts, the response, images, and the parameters that it refuses. `POST /v1/chat/completions` The request and the response have the OpenAI format. We do not change the field names, so an OpenAI client works without a change. Claude, GPT and Gemini models work on this endpoint. See [Supported endpoints](/docs/endpoints). ### Parameters | Field | Notes | | --- | --- | | `model` | Necessary. A model id. See [Models](/docs/models) | | `messages` | Necessary. Roles `system`, `user`, `assistant` and `tool`. `content` is a string, or a list of parts for [images](#images) | | `max_tokens` | The maximum number of output tokens. The default is 16,000 (4,096 on the starter balance) and the maximum is 128,000. Without `max_tokens`, a low balance lowers the default to what the balance can hold, but never below 1,024. We hold the cost of this number of tokens from your balance until the request completes | | `max_completion_tokens` | The newer OpenAI name for `max_tokens`. Send one of the two. If you send the two with different values, you get a `400` error | | `temperature`, `top_p`, `stop`, `seed` | We send them to the model. The ranges are the OpenAI ranges | | `frequency_penalty`, `presence_penalty` | We send them to the model | | `stream`, `stream_options` | See [Streams](/docs/streaming) | | `tools`, `tool_choice`, `parallel_tool_calls` | See [Tool calls](/docs/tool-calling) | | `response_format` | See [Structured output](/docs/structured-output) | | `reasoning_effort`, `verbosity` | We send them to the model. See [Models](/docs/models#models-that-think-first) | | `logprobs`, `top_logprobs` | We send them to the model | | `prompt_cache_key` | See [Prompt cache](/docs/prompt-caching) | > **We refuse a parameter that we do not use** > > If you send a parameter that is not in the table, you get a `400` error. The error names the > parameter, and the code is `unsupported_parameter`. If the model does not support a parameter that > you send, you get the same error. In the two cases, we do not charge for the request. > > We accept six OpenAI fields and do not use them, because they cannot change the answer or the cost: > `user`, `store`, `metadata`, `service_tier`, `safety_identifier` and `n: 1`. ### The response ```json { "id": "chatcmpl-8f2a1c", "object": "chat.completion", "created": 1791331200, "model": "claude-haiku-4-5-20251001", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "The capital of Kenya is Nairobi." }, "finish_reason": "stop" } ], "usage": { "prompt_tokens": 21, "completion_tokens": 8, "total_tokens": 29, "prompt_tokens_details": { "cached_tokens": 0 } }, "kontinent": { "request_id": "req_8f2a1c", "cost_micros": 13, "cost_usd": 0.000013, "list_price_usd": 0.000061, "saved_usd": 0.000048 } } ``` The `kontinent` block is not part of the OpenAI format. An OpenAI client ignores it. It shows the cost of the request, the cost at the official price, and the difference. See [Supported endpoints](/docs/endpoints#the-cost-block). `finish_reason` comes from the model: | Value | Meaning | | --- | --- | | `stop` | The model completed its answer | | `length` | The model used all of `max_tokens` | | `tool_calls` | The model asks you to run a function | | `error` | The stream stopped after it started. See [Reliability](/docs/reliability) | ### Conversations Send all of the conversation in each request. We keep no state between requests. **Python** ```python messages = [{"role": "system", "content": "Answer in one sentence."}] while True: messages.append({"role": "user", "content": input("> ")}) reply = client.chat.completions.create(model="claude-haiku-4-5-20251001", messages=messages) text = reply.choices[0].message.content messages.append({"role": "assistant", "content": text}) print(text) ``` ### Images Some models can read images. To send an image, make `content` a list of parts, as in the OpenAI format: ```json { "model": "claude-sonnet-5-5", "messages": [ { "role": "user", "content": [ { "type": "text", "text": "What is in this picture?" }, { "type": "image_url", "image_url": { "url": "https://example.com/photo.jpg" } } ] } ] } ``` Before you send an image, look at `input_modalities` in [`GET /v1/models`](/docs/models). A model that reads images shows `["text", "image"]`. If you send an image to a model that shows only `["text"]`, you get a `400` error that names `messages`. We do not charge for that request. We charge for an image on the token count that the model gives. The `usage` block of the response shows that count. ### What this endpoint does not do `n` larger than 1 does not work. Send the request more than one time. For the other parameters and endpoints that we do not have, see [Not supported yet](/docs/not-supported-yet). --- ## Streams Source: https://kontinentai.com/docs/streaming Server-sent events on the three formats, and the cost in a chat stream. What we charge when a stream stops or when you close it. Send `stream: true` to get the answer in parts, as server-sent events. This works on the three endpoints that cost money. | Endpoint | The events | | --- | --- | | `/v1/chat/completions` | OpenAI `chat.completion.chunk` frames, then `data: [DONE]` | | `/v1/messages` | The Anthropic events (`message_start` to `message_stop`), as the model sends them | | `/v1/responses` | The OpenAI Responses events (`response.created` to `response.completed`), as the model sends them | We do not change the events of a Messages stream or a Responses stream. The rest of this page is about the chat completions stream. ### A chat completions stream **Python** ```python stream = client.chat.completions.create( model="claude-haiku-4-5-20251001", messages=[{"role": "user", "content": "Count to five."}], stream=True, ) for chunk in stream: if chunk.choices and chunk.choices[0].delta.content: print(chunk.choices[0].delta.content, end="", flush=True) ``` **Node** ```javascript const stream = await client.chat.completions.create({ model: "claude-haiku-4-5-20251001", messages: [{ role: "user", content: "Count to five." }], stream: true, }); for await (const chunk of stream) { process.stdout.write(chunk.choices[0]?.delta?.content ?? ""); } ``` The stream starts with a frame that has the role. Then the content frames come. The last choice frame has the `finish_reason`: ``` data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant"},"finish_reason":null}]} data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"One"},"finish_reason":null}]} data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]} data: [DONE] ``` ### Token counts and cost in a chat stream To get the token counts, send `stream_options`: **Python** ```python stream = client.chat.completions.create( model="claude-haiku-4-5-20251001", messages=[{"role": "user", "content": "Count to five."}], stream=True, stream_options={"include_usage": True}, ) ``` Then the stream has one more frame after the last choice frame. Its `choices` list is empty: ``` data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":14,"completion_tokens":9,"total_tokens":23},"kontinent":{"request_id":"req_8f2a1c","cost_micros":12,"cost_usd":0.000012,"list_price_usd":0.000059,"saved_usd":0.000047}} ``` The `kontinent` block on this frame shows the cost of the request. It is the same amount that we take from your balance. If your code reads `chunk.choices[0]` on each frame, add a check for an empty list. > **Messages and Responses streams have no cost block** > > These formats have no field for the cost, and their clients read the events strictly. Thus, we do > not add a block to them. We record the cost of each request, and the [dashboard](/dashboard) shows > it. ### Tool calls in a chat stream Tool calls come in the stream. `delta.tool_calls` has an `index`. The first frame of each call has the id, the type and the function name. The next frames add parts of `function.arguments`. The SDKs join these parts for you. See [Tool calls](/docs/tool-calling). ### The model's thinking in a chat stream A reasoning model thinks before it answers, and you pay for those tokens. By default the stream does not send the thinking. To get it, send `include_reasoning: true`. Each part of the thinking then comes in `delta.reasoning_content`, apart from `delta.content`. An OpenAI SDK ignores this field, so the answer that your code reads does not change. When the model's supplier gives the number, the usage frame shows the thinking's part of the output tokens in `completion_tokens_details.reasoning_tokens`. ### Keep-alive comments A model can be quiet for a long time while it thinks. If a chat stream sends no data for 15 seconds, we send this comment line: ``` : keep-alive ``` A line that starts with a colon is a comment in server-sent events. The OpenAI SDKs ignore it. If you read the stream with your own code, ignore each line that does not start with `data: `. Some proxies close a quiet connection, and the comment prevents this. Messages and Responses streams have their own `ping` events, so we do not add the comment to them. ### When a stream stops After a stream starts, we cannot send an HTTP status. If the model fails after that point, the last frame of a chat stream has `finish_reason: "error"`, and there is no usage frame. Look at `finish_reason`. If you look only at the content, you cannot see the difference between a full answer and a part of an answer. We charge for the output that the model wrote before the stop. See [Reliability](/docs/reliability). ### When you close the connection > **Warning: A closed connection is not a request without cost** > > The model can continue to write after you close the connection, and our supplier charges us for that > output. Thus, we charge you for it too. After you close the connection, the request has 15 seconds to complete. If it completes in that time, we charge for the token count of the model. If it does not complete, we stop it. Then we charge for the output that the model wrote, and we give back the rest of the amount that we held. That count is never more than your `max_tokens` value. A stream costs the same as the same request without a stream. ### Streams and idempotency A request with `stream: true` and an `Idempotency-Key` header gets a `400` error. To repeat a stream from a cache, we would have to keep each response, so we refuse the header. See [Idempotency](/docs/idempotency). --- ## Tool calls Source: https://kontinentai.com/docs/tool-calling Functions on the chat completions endpoint, the full cycle of a tool call, and the models that accept tools. Messages and Responses use their own tool format. On `/v1/chat/completions`, send `tools` and, if necessary, `tool_choice`. The two fields have the OpenAI format, and we send them to the model. **Python** ```python tools = [{ "type": "function", "function": { "name": "get_weather", "description": "Current weather for a city.", "parameters": { "type": "object", "properties": {"city": {"type": "string"}}, "required": ["city"], }, }, }] reply = client.chat.completions.create( model="claude-sonnet-5-5", messages=[{"role": "user", "content": "What is the weather in Nairobi?"}], tools=tools, ) call = reply.choices[0].message.tool_calls[0] print(call.function.name, call.function.arguments) ``` When the model asks for a tool, `finish_reason` is `tool_calls` and `content` is empty. This means that the model wants you to run the function. ### Send the result back Add the message of the model to the conversation. Then add a message with `role: "tool"` and the `tool_call_id` of the call: **Python** ```python messages = [ {"role": "user", "content": "What is the weather in Nairobi?"}, reply.choices[0].message, { "role": "tool", "tool_call_id": call.id, "content": '{"temp_c": 24, "conditions": "clear"}', }, ] final = client.chat.completions.create(model="claude-sonnet-5-5", messages=messages, tools=tools) print(final.choices[0].message.content) ``` ### Models that accept tools Look at `supported_parameters` in `GET /v1/models`, or at the table on [Models](/docs/models). If you send `tools` to a model that does not show `tools`, you get a `400` error that names the parameter. We do not charge for that request. ### Tool calls in a stream Tool calls also come in a stream. `delta.tool_calls` has an `index`, and the arguments come in small parts. The last frame has `finish_reason: "tool_calls"`. The SDKs join the parts for you. See [Streams](/docs/streaming). ### Messages and Responses On `/v1/messages` and `/v1/responses`, use the tool format of Anthropic or of OpenAI Responses. We send your tools to the model as you wrote them. --- ## Structured output Source: https://kontinentai.com/docs/structured-output JSON mode and json_schema on the chat completions endpoint, the models that accept response_format, and the error for a model that does not. On `/v1/chat/completions`, send `response_format` in one of the two OpenAI forms. **json_schema** ```python import json reply = client.chat.completions.create( model="gemini-3.5-flash", messages=[{"role": "user", "content": "Nairobi, Kenya. Population about 5.5 million."}], response_format={ "type": "json_schema", "json_schema": { "name": "city", "strict": True, "schema": { "type": "object", "properties": { "city": {"type": "string"}, "country": {"type": "string"}, "population": {"type": "integer"}, }, "required": ["city", "country", "population"], "additionalProperties": False, }, }, }, ) print(json.loads(reply.choices[0].message.content)) ``` **json_object** ```python reply = client.chat.completions.create( model="gemini-3.5-flash", messages=[ {"role": "system", "content": "Reply with JSON only."}, {"role": "user", "content": "Nairobi, Kenya. Population about 5.5 million."}, ], response_format={"type": "json_object"}, ) ``` With `json_object`, put the word JSON in your messages, as with OpenAI. Use `json_schema` when you can, because it controls the shape of the answer. ### Models that accept response_format Look for `structured-output` in `supported_parameters` of `GET /v1/models`, or for `json` in the table on [Models](/docs/models). At this time, the GPT and Gemini models show it, and the Claude models do not. If you send `response_format` to a model that does not show it, you get a `400` error. We do not charge for that request: ```json { "error": { "message": "Model \"claude-sonnet-5-5\" does not support `response_format`. See supported_parameters on GET /v1/models for a model that does.", "type": "invalid_request_error", "param": "response_format", "code": "unsupported_parameter" } } ``` > **JSON from a Claude model** > > On `/v1/messages`, use the tool format of Anthropic to get JSON from a Claude model. We send the body > to the model as you wrote it. ### Models that think first GPT and Gemini models think before they write the JSON, and we charge for the thought tokens. If `max_tokens` is only large enough for the JSON, the model can use all of it before it writes the answer. Then you get an empty string. See [Models](/docs/models#models-that-think-first). --- ## Prompt cache Source: https://kontinentai.com/docs/prompt-caching Cached input tokens cost the cached price of the maker times our multiple. Where each format shows the cached tokens, and how to get more cache reads. A coding tool sends a long system prompt and a long history on each turn. Most of each prompt is the same as the prompt before it. The models keep that part in a prompt cache, and a token that comes from the cache costs less. ### The price of a cached token We use the same multiple for each type of token. Each model has three prices in `GET /v1/models`: | Field in `pricing` | What it is | | --- | --- | | `usd_per_million_input` | Input tokens that do not come from the cache | | `usd_per_million_cached_input` | Input tokens that come from the cache (a cache read). Our price is the cached price of the maker times our multiple | | `usd_per_million_cache_write` | Input tokens that go into the cache (a cache write). Only Claude models have this price | The same fields under `pricing.list` give the official prices of the maker. The cost block of each response uses these prices. ### Where the response shows the cached tokens | Endpoint | Fields | | --- | --- | | `/v1/chat/completions` | `usage.prompt_tokens_details.cached_tokens` | | `/v1/messages` | `usage.cache_read_input_tokens` and `usage.cache_creation_input_tokens` | | `/v1/responses` | `usage.input_tokens_details.cached_tokens` | We charge on these counts from the model. We do not make an estimate of them. ### Claude: cache_control on /v1/messages Claude models use the cache at each `cache_control` marker in the request. Claude Code adds these markers for you. On `/v1/messages`, we send the markers to the model as you wrote them. **Python** ```python message = client.messages.create( model="claude-sonnet-5-5", max_tokens=1024, system=[{"type": "text", "text": long_instructions, "cache_control": {"type": "ephemeral"}}], messages=[{"role": "user", "content": "Summarise section 2."}], ) print(message.usage.cache_read_input_tokens, message.usage.cache_creation_input_tokens) ``` > **Claude on the chat completions endpoint** > > The chat completions format has no `cache_control` marker. For the best cache price on Claude, use > `/v1/messages`. ### GPT: prompt_cache_key GPT models put long prompts into the cache automatically. On `/v1/chat/completions` and `/v1/responses`, send `prompt_cache_key` to keep the turns of one conversation together: **Python** ```python client.chat.completions.create( model="gpt-5.5", messages=messages, prompt_cache_key=f"conversation-{conversation_id}", ) ``` Use a value that stays the same for one conversation and is different for each conversation. A user id is a good value. A new random value for each request stops the cache. ### How to get more cache reads - **Put the part that does not change first.** The cache matches the start of a prompt. A timestamp at the start of each message stops the cache. - **Keep the order of tools and messages the same.** A different order is a different prompt. - **Use long prefixes.** A short system prompt does not give much of a decrease in cost. --- ## Reliability Source: https://kontinentai.com/docs/reliability What we do when a supply route fails or is slow. Why a failed request costs no money, and what you pay when a stream stops after it starts. ### More than one route for each model Each model has a list of supply routes in a fixed order. We send a request to the first route. If that route fails before the answer starts, we send the same request to the next route. You do not have to change your code, and you get one answer. We try the next route when the first route: - does not answer, or closes the connection. - answers with `401`, `402`, `403`, `404`, `408`, `409`, `425`, `429` or a `5xx` status. - says that it cannot serve this model at this time. - starts a stream, but the first event of the stream is an error. - does not send the first part of the answer in the time limit. See the next section. We do not try the next route when the model refuses your request for a different reason, for example a prompt that is too long or a tool schema that is not correct. The next route would refuse it too. You get a `400` error with the code `upstream_invalid_request` and the message from the model. ### The time limit for the first byte Some routes do not refuse a request. They keep the request and do not send an answer. Thus, each route has a time limit for the first byte of the answer: | Request | First byte | Time limit | | --- | --- | --- | | A stream | The first event of the stream | 15 seconds | | A stream from a reasoning model on `/v1/chat/completions` | The first event of the stream | 45 seconds | | No stream | The response. This is the full answer. | 15 seconds plus 1 second for each 20 tokens of `max_tokens` (maximum 300 seconds) | Some routes are slow but usually answer. These routes have a time limit of 30 seconds for a stream. The last route in the order has a time limit of 60 seconds or more. If that route is slow, a slow answer is better than no answer. If the last route is at the end of the order because it failed (see the next sections), the route before it gets the 60 second limit. A reasoning model can think before it sends the first event of a `/v1/chat/completions` stream, thus it has a longer limit. The `/v1/messages` and `/v1/responses` streams start with an event at once, thus they keep the 15 second limit. If a route does not send the first byte in the time limit, we stop the request to that route and send the request to the next route. We do not charge for the stopped request, because it did not give you a part of the answer. After the first byte gets to you, the time limit stops. We do not stop an answer after it starts. ### One more try for a slow route Sometimes all the routes fail, and one or more of them failed only because of the time limit. These routes did not refuse the request. They were slow, and their answer possibly was about to start. Thus, we send the request one more time to one of these routes, with the 60 second limit. A request without a stream also gets 1 second for each 20 tokens of `max_tokens`, to a maximum of 300 seconds. We choose the route that failed the fewest times in sequence. If two routes have the same record, we choose the route that is first in the usual order. We make this try one time only for each request. If you close the connection, we do not make this try. If the route answers, you pay for that answer one time, as usual. If it does not answer, you get the `502` error and we do not charge you. ### The route order changes when a route fails We keep a record of each route for each model. When a route fails three times in sequence, we move it to the end of the order for 5 minutes. A route that did not answer in the time limit has one failure. The one more try for a slow route does not add a second failure. If the route says that it has too much load (a `429` or `503` error with that message), we move it to the end after one failure. After the 5 minutes, one request tries the route in its usual position: - If the route answers, it stays in its usual position. - If the route fails, it goes to the end of the order again. The time doubles each time, to a maximum of 30 minutes. We do not remove a route from the order. If all the other routes fail, we try the routes at the end too. If all the routes are at the end, we try them all, and the route that failed first is first. Thus, the route order does not stop a request. An error that comes from your request, for example a `400` for a prompt that is too long, does not change the route order. ### A failed request costs no money Before a request starts, we hold the maximum cost from your balance. If no route can answer, we give back all of that amount. The error is a `502` with this message: ```json { "error": { "message": "Model claude-sonnet-5-5 is not available right now. Nothing was charged. Retry in a moment.", "type": "api_error", "param": null, "code": "PROVIDER_ERROR" } } ``` A `503` with the code `SUPPLY_NOT_CONFIGURED` means that the model has no route at this time. We do not charge for that request either. Send the request again later, or use a different model. The OpenAI SDKs and the Anthropic SDKs retry `502` and `503` errors with backoff. ### A stream that stops after it starts After the first part of a stream gets to you, we cannot change the route. A second route would start a different answer. Thus, if the route fails after that point, the stream stops: | Endpoint | What you get | | --- | --- | | `/v1/chat/completions` | If the model sends an error event, the last frame has `finish_reason: "error"`, and there is no usage frame. If the connection closes, the stream stops without `data: [DONE]` | | `/v1/messages`, `/v1/responses` | The error event of the model, as the model sent it, or a closed connection | > **Warning: You pay for the output that the model wrote** > > The model writes tokens until the stream stops, and our supplier charges us for them. Thus, we > charge for the output tokens that the model wrote before the stop. We do not charge for the remainder > of the request, and we give back the rest of the amount that we held. If the stream did not give a final token count, we count the output that we received. That count is never more than the `max_tokens` value of your request. We record that the count is an estimate. ### If you close the connection If you close the connection during a stream, the model can continue for a short time. We wait for 15 seconds: - If the answer completes in that time, we charge for the token count of the model. - If the answer does not complete, we stop it and charge for the output that the model wrote. Thus, a closed connection does not make a request cost less. ### Check the status The [status page](/status) shows if the API answers. For each model, it also shows the last hour of real requests: | Word | Meaning | | --- | --- | | Operational | 95% or more of the requests got an answer, and the median time to the first byte is 10 seconds or less | | Degraded | Less than 95% of the requests got an answer, or the median time to the first byte is more than 10 seconds | | Down | Less than half of the requests got an answer, and three or more requests did not get an answer | | No recent requests | No request used the model in the last hour | A request that a second route answered is a request that got an answer. The median time to the first byte uses streams only, because the first byte of a request without a stream is the full answer. Errors that come from your request, and `402` and `429` errors, are not in the count. If you think that a model fails too frequently, send the `x-request-id` of a failed request to team@kontinentai.com. --- ## Prices and charges Source: https://kontinentai.com/docs/pricing Each model costs a fixed fraction of the official price of its maker, for each token. How we hold and charge the cost, and where your balance comes from. ### The rule Each model costs a fixed fraction of the official price of its maker, for each token. Input, output, cached input and cache writes use the same fraction. If a maker charges more for a long prompt, our price for that prompt changes by the same step. You pay for the tokens that you send and the tokens that the model returns, counted on your request. `GET /v1/models` gives our price for each million tokens, the official price (`pricing.list`) and the fraction (`pricing.multiple_of_list`). It needs no key. It is always correct for the figures on this page. See "The catalogue" above for the full table of models, prices and capabilities. The table shows the frontier models first. The open models (Kimi, GLM, Qwen and MiniMax) come after them. Each open model has its own multiple, and it is smaller than the frontier multiple. To check an official price, open the price page of the maker: [Anthropic](https://platform.claude.com/docs/en/about-claude/pricing), [OpenAI](https://openai.com/api/pricing/), [Google](https://ai.google.dev/gemini-api/docs/pricing), [Moonshot AI](https://platform.kimi.ai/docs/pricing/chat), [Z.ai](https://docs.z.ai/guides/overview/pricing), [Qwen](https://www.alibabacloud.com/help/en/model-studio/model-pricing) and [MiniMax](https://platform.minimax.io/docs/guides/pricing-paygo). ### The cost on each response A response without a stream has a `kontinent` block: | Field | What it is | | --- | --- | | `cost_micros` | The cost in micro-dollars. One micro-dollar is $0.000001. This is the amount that we take from your balance. Add these values to get an exact total | | `cost_usd` | The same cost in US dollars | | `list_price_usd` | The cost of the same tokens at the official price | | `saved_usd` | `list_price_usd` minus `cost_usd` | A chat completions stream has the block on its usage frame. A Messages stream and a Responses stream do not have the block. See [Supported endpoints](/docs/endpoints#the-cost-block). The "Saved this month" figure on the dashboard is the sum of the savings of your requests. ### Your balance The balance is in US dollars, and it does not expire. There is no subscription and no minimum monthly payment. A new account gets a starter balance of $0.02. Until you add balance, you can use it on these models only: `claude-haiku-4-5-20251001` (/v1/messages, /v1/chat/completions); `gemini-3.5-flash` (/v1/chat/completions). To get more balance, top up on the [balance page](/dashboard/balance), in US dollars or in your own currency. A top-up starts at $1 by mobile money or bank transfer, and at $3 by card. You can also enter a redemption code there. The balance page also shows each charge. ### How we charge a request Before a request starts, we do not know how long the answer will be. Thus, we use three steps: 1. **Hold.** We hold the maximum cost from your balance. The maximum cost uses all of your `max_tokens` as output. If your balance is too low, you get a `402` error, and the request does not start. 2. **Run.** The model answers. 3. **Charge.** The model gives the real token counts. We charge for those tokens and give back the rest of the amount that we held. As a result: - An amount that we hold during a request is not a charge. Most of it comes back. - If the request fails, all of the amount comes back. A failed request costs no money. - We charge on the token counts of the model, not on our estimate. ### How we round a charge We round each charge up to the next micro-dollar. ### Thought tokens Some models think before they answer. We charge for that output as output tokens, but the response does not show it. On a short answer, this can be most of the cost. In `GET /v1/models`, these models have `"reasoning": true`. ### Streams A stream costs the same as the same request without a stream. If you close the connection, we charge for the output that the model wrote. See [Streams](/docs/streaming#when-you-close-the-connection). ### What we do not do - We do not use monthly plans, seats or tiers. - We do not charge for a failed request. - We do not change a price without notice. We write each price change in the [changelog](/docs/changelog). A price change does not change the value of a balance that you have. --- ## Rate limits Source: https://kontinentai.com/docs/rate-limits Sixty requests a minute for each API key. The headers that show your position, and what to do when you get a 429 error. **60 requests a minute for each API key.** We use a fixed window that starts again each minute. The limit applies to `/v1/chat/completions`, `/v1/messages`, `/v1/messages/count_tokens` and `/v1/responses` together. `GET /v1/models` has no limit of this type. Other limits: - An account that has only the starter balance can send 5 requests a minute. - The dashboard playground has a limit of 20 requests a minute for each account. ### Headers Each response from an endpoint with a limit has these headers. A `429` response has them too. | Header | What it is | | --- | --- | | `X-RateLimit-Limit` | The limit for this window | | `X-RateLimit-Remaining` | The number of requests that you can send before the window ends | | `X-RateLimit-Reset` | The time when the window ends, in Unix seconds | A `429` response also has `Retry-After: 60`. ### When you get a 429 error Wait before you send the request again. The `openai` and `anthropic` SDKs retry `429` and `5xx` errors with backoff. To change the number of retries, set `max_retries` on the client. If you write your own retry code, retry only `409`, `429`, `500`, `502` and `503`. Do not retry `400`, `401`, `402`, `403`, `404` or `422`. These errors do not change when you send the request again: **Python** ```python import time, random, openai def with_retry(fn, attempts=5): for i in range(attempts): try: return fn() except openai.RateLimitError as e: wait = float(e.response.headers.get("retry-after", 0)) or (2 ** i + random.random()) time.sleep(wait) except openai.APIStatusError as e: # 409 and 5xx can pass on a second try. 402 needs more balance; any other 4xx needs a # change to the request. A retry changes nothing for those. if e.status_code not in (409, 500, 502, 503): raise time.sleep(2 ** i + random.random()) raise RuntimeError("still failing after retries") ``` ### Coding tools Claude Code, Codex and OpenCode can send many requests in one minute, for example when they start subagents. If they get `429` errors, use a second key for the second tool. Each key has its own limit. ### Requests at the same time There is no limit on the number of requests that run at the same time. But each request holds its maximum cost from your balance until it completes. Thus, many large requests at the same time can get a `402` error, although each one alone has enough balance. Make `max_tokens` smaller, or add balance. ### What counts A request counts when we accept it, also if the model then fails. A request that the limit refuses does not cost money. ### If you need a higher limit Write to team@kontinentai.com. Tell us what you make and the rate that you need. --- ## Errors Source: https://kontinentai.com/docs/errors The two error formats and the body for each status. Which errors to retry, and how to report an error so that we can find it. An error has the format of its endpoint, so that the error classes of each SDK work. `/v1/chat/completions`, `/v1/responses` and `/v1/models` use the OpenAI format: ```json { "error": { "message": "Insufficient balance: $0.001230 required, $0.000400 available", "type": "insufficient_quota", "param": null, "code": "INSUFFICIENT_BALANCE", "details": { "required_micros": 1230, "required_usd": 0.00123, "available_micros": 400, "available_usd": 0.0004 } } } ``` `/v1/messages` and `/v1/messages/count_tokens` use the Anthropic format: ```json { "type": "error", "error": { "type": "billing_error", "message": "Insufficient balance: $0.001230 required, $0.000400 available" }, "request_id": "req_8f2a1c" } ``` | Field | What it is | | --- | --- | | `message` | A sentence for a person to read | | `type` | The error type that the SDKs use | | `code` | Our code. It does not change, so your code can use it. OpenAI format only | | `param` | The field that caused the error, if there is one. OpenAI format only | | `details` | More data, where it helps. OpenAI format only | The Anthropic format uses the same types, with one change: `insufficient_quota` becomes `billing_error`. ### Each status | Status | `type` | Meaning | Retry | | --- | --- | --- | --- | | `400` | `invalid_request_error` | The request has a problem, and the message names it. Examples are a parameter that we do not use and a model on the wrong endpoint. `Idempotency-Key` with `stream: true` and a request that the model refused also get this error | No | | `401` | `authentication_error` | The request has no key, a key in the wrong format or a revoked key. The response has a `WWW-Authenticate` header | No | | `402` | `insufficient_quota` | Your balance is too low. `details` shows the amount that the request needed and the amount that you had | After you add balance | | `403` | `permission_error` | Code `STARTER_BALANCE_ONLY`. The account has only the starter balance, and this model is not a starter model. The message names the models that you can use | After you add balance | | `404` | `not_found_error` | We do not have this model. `GET /v1/models` has the list | No | | `409` | `invalid_request_error` | A request with the same `Idempotency-Key` did not complete yet | Yes, after a short time | | `422` | `invalid_request_error` | The body is not correct on `/v1/chat/completions`, or an `Idempotency-Key` came again with a different body, or a starter prompt is too long. `details` has the fields | No | | `429` | `rate_limit_error` | Too many requests. See [Rate limits](/docs/rate-limits) | Yes, after `Retry-After` | | `500` | `api_error` | Our error. The body has `request_id`. Send it to us | Yes, with backoff | | `502` | `api_error` | No route could serve the model. We did not charge you. See [Reliability](/docs/reliability) | Yes, with backoff | | `503` | `api_error` | Code `SUPPLY_NOT_CONFIGURED`. The model has no route at this time. We did not charge you | Yes, later, or use a different model | On `/v1/messages`, a body that is not correct gets a `400` error, not a `422` error, because the Anthropic API does the same. > **402, not 429** > > OpenAI uses `429` with the type `insufficient_quota` for an empty balance. We keep the type, but we use > `402`. A `429` tells a client to wait and try again, and that cannot help an empty balance. A client > that reads `type` sees the OpenAI meaning. A client that reads only the status sees the correct > meaning. ### Read an error **Python** ```python import openai try: reply = client.chat.completions.create(model="claude-haiku-4-5-20251001", messages=messages) except openai.APIStatusError as e: print(e.status_code) # 402 print(e.message) # "Insufficient balance: $0.001230 required, …" print(e.body["error"]["type"]) # "insufficient_quota" print(e.response.headers["x-request-id"]) ``` **Node** ```javascript import OpenAI from "openai"; try { await client.chat.completions.create({ model: "claude-haiku-4-5-20251001", messages }); } catch (err) { if (err instanceof OpenAI.APIError) { console.log(err.status, err.message, err.type, err.headers["x-request-id"]); } } ``` ### Report an error Each response has an `x-request-id` header. A response without a stream also has the same value in `kontinent.request_id`. Send the id, your request and the result that you expected to team@kontinentai.com. See [Request ids](/docs/request-ids). The [status page](/status) shows if the API answers and which models answered in our last test. ### The dashboard routes `/api/v1/*` uses a different format, `{"ok": false, "error": {...}}`. The dashboard uses these routes. They are not part of the public API. Do not use them in your code. --- ## Idempotency Source: https://kontinentai.com/docs/idempotency Send a request again after a timeout and pay one time only. An Idempotency-Key works on the chat completions, Messages and Responses endpoints. After a network timeout, you do not know if the request completed. Send an `Idempotency-Key` header, and then a retry is safe. The header works on `/v1/chat/completions`, `/v1/messages` and `/v1/responses`. **curl** ```bash curl https://api.kontinentai.com/v1/chat/completions \ -H "Authorization: Bearer $KONTINENT_API_KEY" \ -H "Idempotency-Key: 8f2a1c94-6e3b-4d5a-9c11-2f7e0a1b3d44" \ -H "Content-Type: application/json" \ -d '{"model":"claude-haiku-4-5-20251001","messages":[{"role":"user","content":"Hello"}]}' ``` **Python** ```python import uuid key = str(uuid.uuid4()) # generated ONCE, reused across every retry of this request reply = client.chat.completions.create( model="claude-haiku-4-5-20251001", messages=[{"role": "user", "content": "Hello"}], extra_headers={"Idempotency-Key": key}, ) ``` Make the key one time, before the first try. Use the same key for each retry of that request. A new key for each retry gives no protection. ### What happens when you send the key again | Situation | Response | | --- | --- | | Same key, same body, and the first request completed | The first response again, with the header `Idempotent-Replay: true`. We do not charge for it | | Same key, same body, and the first request did not complete yet | `409`. Wait a short time and try again | | Same key, different body | `422`. One key is for one request | | Same key, more than 24 hours later | We use it as a new key. We keep keys for 24 hours | A repeated response is the same as the first response, with the same `id`, token counts and cost. We do not run the request again. ### Two rules that make it safe - **A failed request releases its key.** You can retry immediately with the same key. - **A key that does not complete in five minutes becomes available again.** A request that stops without an answer does not lock the key. The database records each key. Thus, if two retries arrive at the same time, only one of them runs. ### Not with a stream A request with `stream: true` and an `Idempotency-Key` header gets a `400` error with the code `idempotency_unsupported_for_stream`. To repeat a stream, we would have to keep each streamed response. We refuse the header, so that you do not think that you have a protection that you do not have. ### When to use it Use it on each request where a double charge is a problem and a retry can occur: a queue worker, a webhook handler, or a call through a proxy with its own timeout. It adds no cost when the first try succeeds. --- ## Request ids Source: https://kontinentai.com/docs/request-ids Each response has a request id. Where to find it, what to log, and what we can find in our records when you send it to us. Each response from each endpoint has an `x-request-id` header. A response without a stream also has the id in its body: ```json "kontinent": { "request_id": "req_8f2a1c", "cost_micros": 13, "cost_usd": 0.000013, "list_price_usd": 0.000061, "saved_usd": 0.000048 } ``` The header and the body field have the same value. The header is also on errors, where the body can be short. The body field puts the id in your own logs, beside the answer. ### Log it **Python** ```python reply = client.chat.completions.with_raw_response.create( model="claude-haiku-4-5-20251001", messages=messages, ) print(reply.headers["x-request-id"]) parsed = reply.parse() print(parsed.model_extra["kontinent"]["request_id"]) ``` **Node** ```javascript const res = await client.chat.completions .create({ model: "claude-haiku-4-5-20251001", messages }) .withResponse(); console.log(res.response.headers.get("x-request-id")); console.log(res.data.kontinent.request_id); ``` On an error, the SDKs give the headers on the exception. A `500` error and each error from `/v1/messages` also have the id in the body. See [Errors](/docs/errors). ### What the id gives us A request id finds one usage record: the model, the token counts, the cost, the time, and the route that served the request. With the id, we can find your request immediately. Without the id, we must search your account in a time range. ### It is not a secret The id identifies a request, not you, and it contains no part of your key. You can put it in an issue or a message. --- ## Limits and your data Source: https://kontinentai.com/docs/limits-and-data What we keep from a request and what we do not keep, who processes your prompts, and the limits on size, length and requests. ### What we keep We do not keep your prompts or the answers of the model. A request goes through us to the model, and the answer goes back to you. We record data about the request: the model, the token counts, the cost, the time, the route that served it, and the request id. Our database has no column for the text. The full policy is on the [privacy page](/privacy). We record a failed or refused request in the same way: the status, the error code and the model. We delete these records after 90 days. ### Who processes your prompts We do not train a model on your requests. To answer a request, we send it to third-party AI suppliers and model providers. They process your prompts and the answers, possibly in other countries, and their terms apply there. ### Limits | Limit | Value | | --- | --- | | Requests | 60 a minute for each API key. See [Rate limits](/docs/rate-limits) | | Requests at the same time | No separate limit. Each request holds its maximum cost from your balance until it completes, so many large requests at the same time can get a `402` error | | Prompt length | The context window of the model. See `context_window` in `GET /v1/models` | | Output length | 128,000 tokens for `max_tokens`, or less if the model has a lower limit | | Request body | 100 MB, the limit of the network that we use | | Request time | We set no time limit. For a long answer, use a stream. A chat stream sends a keep-alive comment after 15 seconds without data, so that a proxy does not close it | | Starter balance only | The starter models, 4,096 output tokens, prompts of 40,000 characters and 5 requests a minute | > **Note** > > If you need a higher limit, write to team@kontinentai.com. Tell us what you make and the rate that you > need. ### Report a problem Send the `x-request-id` of the response, your request and the result that you expected to team@kontinentai.com. The [status page](/status) shows if the API answers and which models answered in our last test. See [Request ids](/docs/request-ids). --- ## Not supported yet Source: https://kontinentai.com/docs/not-supported-yet The endpoints and parameters that this API does not have. Read this list before you find out from a 404 error. The API has five routes. See [Supported endpoints](/docs/endpoints). This page lists what the API does not have. ### Endpoints | Endpoint | Status | | --- | --- | | `POST /v1/embeddings` | Not available | | `POST /v1/completions` (the old OpenAI format) | Not available, and we do not plan it. Use chat completions | | `POST /v1/images/generations` | Not available | | `POST /v1/audio/transcriptions`, `POST /v1/audio/speech` | Not available | | `POST /v1/moderations` | Not available | | `POST /v1/messages/batches` and OpenAI batches | Not available | | Assistants, threads, files and fine-tuning | Not available | | The Gemini format (`generateContent`) | Not available. Use Gemini models on `/v1/chat/completions` | | A balance endpoint on `/v1` | Not available. The [balance page](/dashboard/balance) shows your balance. Each response without a stream shows its cost in `kontinent.cost_micros` | ### Parameters on /v1/chat/completions | Parameter | Status | | --- | --- | | `n` | We accept `n: 1`. A larger value gets a `400` error. Send the request more than one time | | `logit_bias` | We refuse it with a `400` error, because it can change the answer | | `user` | We accept it and do not use it. We already know the key of each request | | `store`, `metadata`, `safety_identifier` | We accept them and do not use them. They cannot change the answer or the cost | | `service_tier` | We accept it and do not use it. We have no priority service | A parameter that is not on [Chat completions](/docs/chat-completions) gets a `400` error that names the parameter. We do not charge for that request. The code is `unsupported_parameter`. On `/v1/messages` and `/v1/responses`, we send the body to the model as you wrote it. The model makes the decision about each field. > **No dates** > > We do not give dates for new features. When a feature is available, we write it in the > [changelog](/docs/changelog). If you need a feature from this page, tell us what you make at > team@kontinentai.com. --- ## Changelog Source: https://kontinentai.com/docs/changelog Each change to the public API that can affect your code. Model ids, prices, response fields and error formats, with the newest change first. We mark each change that can break your code. All other changes add to the API. ### 9 October 2026 - **Top-ups.** Add balance on the [balance page](/dashboard/balance), in US dollars or in your own currency: from $1 by mobile money or bank transfer, and from $3 by card. ### October 2026 The first version of the Kontinent AI API. - **Five routes** on `https://api.kontinentai.com`: `POST /v1/chat/completions` (OpenAI format), `POST /v1/messages` and `POST /v1/messages/count_tokens` (Anthropic format), `POST /v1/responses` (OpenAI Responses format), and `GET /v1/models`. See [Supported endpoints](/docs/endpoints). - **Claude, GPT and Gemini models.** Each model costs a fixed fraction of the official price of its maker. See [Models](/docs/models). - **Keys** start with `sk-kont-`. Each endpoint that costs money accepts the key in `Authorization: Bearer` or in `x-api-key`. - **A cost block** on each response without a stream: `cost_usd`, `list_price_usd` and `saved_usd`. A chat stream has the block on its usage frame. A Messages stream and a Responses stream do not have it. - **More than one supply route for each model.** If a route fails before the answer starts, the request goes to the next route. See [Reliability](/docs/reliability). - **Balance** comes from the starter balance and from redemption codes. At this time, you cannot buy balance on the site. - **Setup pages** for [Claude Code](/docs/claude-code), [Codex](/docs/codex), [OpenCode](/docs/opencode), the [OpenAI SDK](/docs/openai-sdk) and the [Anthropic SDK](/docs/anthropic-sdk). --- ## Move from OpenAI Source: https://kontinentai.com/docs/migrating-from-openai Change the base URL and the key, and keep the OpenAI SDK. What stays the same, what is different, and three checks to do before you move traffic. Change the base URL and the key. Keep the `openai` SDK, your request code, your stream code and your tool calls. **Python** ```python import os from openai import OpenAI client = OpenAI( api_key=os.environ["KONTINENT_API_KEY"], base_url="https://api.kontinentai.com/v1", ) ``` **Node** ```javascript import OpenAI from "openai"; const client = new OpenAI({ apiKey: process.env.KONTINENT_API_KEY, baseURL: "https://api.kontinentai.com/v1", }); ``` **Env only** ```bash # Most tools read these, so often no code change at all. export OPENAI_API_KEY="$KONTINENT_API_KEY" export OPENAI_BASE_URL="https://api.kontinentai.com/v1" ``` ### What stays the same - Chat completions and the Responses API, with streams and tool calls. - Structured output on GPT and Gemini models. - The parameters `temperature`, `top_p`, `seed`, `stop` and `logprobs`. - The `usage` object, `finish_reason`, `Idempotency-Key` and the error format. ### The model ids The GPT models have the same ids as at OpenAI, for example `gpt-5.5` and `gpt-5`. The same key also gives you access to Claude and Gemini models. See [Models](/docs/models) for the full list. | If you use | A model to try first | | --- | --- | | A GPT model that we serve | The same id | | A small, fast model for classification or extraction | `gemini-3.5-flash` or `claude-haiku-4-5-20251001` | | A large model for code | `claude-sonnet-5-5` or `gpt-5.5` | These are suggestions, not equivalents. Do your own tests on your own prompts. ### Three checks before you move traffic 1. **Each model id exists.** `GET /v1/models` needs no key, so a short script can do this check. 2. **Each capability that you use is in the catalogue.** Look at `supported_parameters`. If you send `tools` or `response_format` to a model that does not show it, you get a `400` error. 3. **Each model works on your endpoint.** Claude and Gemini models do not work on `/v1/responses`. See [Supported endpoints](/docs/endpoints). ### What is not available Embeddings, images, audio, moderations, assistants and batches. `n` larger than 1. See [Not supported yet](/docs/not-supported-yet). If your app uses embeddings, keep that part at its current provider. You can use two clients with two base URLs. ### Errors A `402` error means that your balance is too low. It has the type `insufficient_quota`, so the error classes of the SDK work. The rate limit is 60 requests a minute for each key. See [Errors](/docs/errors) and [Rate limits](/docs/rate-limits). > **Run the two providers together** > > Use two clients with two base URLs, and select one with a flag. Then you can compare the quality and > the cost on your own traffic. To go back, change the flag. --- ## Switch from another provider Source: https://kontinentai.com/docs/switch-provider Move code from the Anthropic API, OpenRouter or a different relay. What to change, what stays the same, and how we select a supply route. The API uses the formats of OpenAI and Anthropic. Thus, if your code uses the OpenAI SDK or the Anthropic SDK, you change only the base URL, the key and, sometimes, the model id. ### From the Anthropic API 1. Set the base URL to `https://api.kontinentai.com`. Do not add `/v1`. 2. Use your Kontinent AI key in place of your Anthropic key. 3. Keep your model ids. We use the Anthropic ids, for example `claude-sonnet-5-5`. See [Anthropic SDK](/docs/anthropic-sdk) and [Claude Code](/docs/claude-code). ### From OpenRouter or a different relay 1. Set the base URL to `https://api.kontinentai.com/v1`. 2. Use your Kontinent AI key. 3. Change the model ids. OpenRouter puts the maker before the model, for example `anthropic/...`. We use the id without the maker. `GET /v1/models` gives each id, and it needs no key. An unknown id gets a `404` error. We never send your request to a different model. ### How we select a supply route Each model has more than one supply route. If a route fails before the answer starts, we send the request to the next route for the same model. We do not send it to a different model. See [Reliability](/docs/reliability). You cannot select the route. On `/v1/chat/completions`, the OpenRouter fields `provider`, `models` and `route` get a `400` error that names the field. We do not ignore them in silence. ### Payment We use a balance in US dollars that you get before you use the API. There is no monthly payment and no monthly minimum. You add balance with a top-up from $1, or with a redemption code. See [Prices and charges](/docs/pricing). ### Three checks before you move traffic 1. **Each model id that you use exists here.** `GET /v1/models` needs no key, so a short script can do this check. 2. **Each model works on your endpoint.** See [Supported endpoints](/docs/endpoints). 3. **Your error code reads `error.type`.** A low balance is a `402` error with the type `insufficient_quota` (or `billing_error` on `/v1/messages`), not a `429` error. > **What is not here** > > We do not have embeddings, images, audio or batches. We do not have a log of your requests that you > can read in the dashboard. See [Not supported yet](/docs/not-supported-yet). --- ## Frameworks Source: https://kontinentai.com/docs/sdk-compatibility Settings for LangChain, LlamaIndex, the Vercel AI SDK and LiteLLM, and the two settings that cause most connection problems. A framework that can use the OpenAI chat completions API works with Kontinent AI. Set the base URL to `https://api.kontinentai.com/v1` and use a Kontinent AI key. For the SDKs, see [OpenAI SDK](/docs/openai-sdk) and [Anthropic SDK](/docs/anthropic-sdk). ### LangChain **Python** ```python import os from langchain_openai import ChatOpenAI llm = ChatOpenAI( model="claude-sonnet-5-5", api_key=os.environ["KONTINENT_API_KEY"], base_url="https://api.kontinentai.com/v1", ) ``` **JS** ```javascript import { ChatOpenAI } from "@langchain/openai"; const llm = new ChatOpenAI({ model: "claude-sonnet-5-5", apiKey: process.env.KONTINENT_API_KEY, configuration: { baseURL: "https://api.kontinentai.com/v1" }, }); ``` Use the `langchain-openai` package. Tool calls work through it on the models that show `tools`. ### LlamaIndex **Python** ```python import os from llama_index.llms.openai_like import OpenAILike llm = OpenAILike( model="claude-sonnet-5-5", api_key=os.environ["KONTINENT_API_KEY"], api_base="https://api.kontinentai.com/v1", is_chat_model=True, is_function_calling_model=True, ) ``` > **Warning: Set is_chat_model=True** > > If you do not set it, `OpenAILike` sends requests to the old `/v1/completions` endpoint. We do not > have that endpoint, so you get a `404` error. ### Vercel AI SDK Use the `@ai-sdk/openai-compatible` package: **TypeScript** ```typescript import { createOpenAICompatible } from "@ai-sdk/openai-compatible"; import { streamText } from "ai"; const kontinent = createOpenAICompatible({ name: "kontinent", apiKey: process.env.KONTINENT_API_KEY, baseURL: "https://api.kontinentai.com/v1", includeUsage: true, }); const result = streamText({ model: kontinent("claude-sonnet-5-5"), prompt: "Explain server-sent events in two sentences.", }); ``` `includeUsage: true` asks for the usage frame at the end of a stream. That frame also has our `kontinent` cost block. ### LiteLLM **Python** ```python import os from litellm import completion response = completion( model="openai/claude-sonnet-5-5", messages=[{"role": "user", "content": "Hello"}], api_key=os.environ["KONTINENT_API_KEY"], api_base="https://api.kontinentai.com/v1", ) ``` The `openai/` prefix tells LiteLLM to use its OpenAI handler. Without the prefix, LiteLLM tries to find the provider from the model name. ### Other tools If a tool accepts a base URL and a key, it can work with Kontinent AI. Two settings cause most problems: - **A path after `/v1`.** The base URL is `https://api.kontinentai.com/v1`. Most clients add `/chat/completions`. If you add it too, the client sends requests to `/v1/chat/completions/chat/completions` and gets a `404` error. - **The old completions endpoint.** Some tools use `/v1/completions` by default. We do not have it. Look for a setting for chat models. If a tool does not work, tell us the name and the version of the tool at team@kontinentai.com. --- ## Not covered here, deliberately `/api/v1/*` is the dashboard's own surface. It is session-authenticated, it is not versioned for third parties, and it will change without notice. **Do not write against it.** Everything a developer is meant to call lives under `/v1`. Human version of all of the above: https://kontinentai.com/docs