API reference
konata.ai speaks the Anthropic and OpenAI wire formats.
Point a client at the gateway, authenticate with our token, and you are all set:
curl https://api.konata.ai/v1/messages \
-H "x-api-key: $KONATA_TOKEN" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{"model":"claude-opus-4-8","max_tokens":64,"messages":[{"role":"user","content":"hi"}]}' Authentication
To authenticate, you need to pass a token from API tokens in the request headers:
Authorization: Bearer <token>
# or
x-api-key: <token> Token scopes
| scope | can | cannot |
|---|---|---|
| Inference | call models, get models list | read your balance or transactions |
| Account | read balance and transactions | call a model, so it can never spend |
| Inference + account | everything above |
Using a token outside its scope returns 403.
Inference endpoints
The endpoint decides the dialect, not the model: any model can be called through any of them, and konata translates in both directions.
However, it is recommended to use the model’s native dialect whenever possible, to avoid unnecessary translations.
| endpoint | dialect |
|---|---|
POST /v1/messages | Anthropic Messages |
POST /v1/messages/count_tokens | Anthropic token counting |
POST /v1/chat/completions | OpenAI Chat Completions |
POST /v1/responses | OpenAI Responses (+ WebSocket) |
Choosing a pool
A model is usually priced by several pools. Unless you ask otherwise, konata uses the model’s
default pool (usually cheapest), and re-routes to a cheaper pool when the chosen one is
out of capacity. See pool routing for the whole resolution order, and set your own
preferences on the pools settings page.
Per request:
| header | effect |
|---|---|
x-knt-pool: <pool> | explicitly specify a pool to use, ignore preferences, never reroute |
x-knt-allow-cheaper: true | allow re-routing to a cheaper pool |
x-knt-tag: <string> | tag the request – visible in transactions history, can be used to track per-message costs |
Alternatively, you can specify a pool to use directly in the model name, delimited by a colon (same semantics as the x-knt-pool header):
{
// use pool "fastest" and model "claude-opus-5"
"model": "fastest:claude-opus-5",
...
} Every response includes a x-knt-served-pool header, containing the pool that actually served the response.
Catalog and balance
GET /v1/models
Lists the models you can call.
This endpoint primarily serves as a compatibility shim for various harnesses and auto-detects the format based on the request. For custom clients, we recommend to use our custom endpoint below.
GET /_konata/models
Our own catalog endpoint, with the per-pool prices behind each model:
[
{
"id": "claude-opus-4-8",
"display_name": "Opus 4.8",
"family": "claude",
"context_window": 200000,
"aliases": ["claude-opus-latest"],
"default_pool": "cheapest",
// model's native dialect, "ant_messages" | "oai_completions" | "oai_responses"
"native_dialect": "ant_messages",
"pools": [
{
"name": "budget",
"supports_cache": true,
// pool's dialect, usually the same as the model's
"dialect": "ant_messages",
"rates": {
// in nanodollars, see below
"input": "3000",
"output": "15000",
"cache_read": "300",
"cache_write": "3750",
"cache_write_1h": "6000"
}
}
]
}
] Rates are nanodollar strings, i.e. integer 1e-9 USD per token.
Example:
3000 n$/tok = 3000 * 1e-9 = 0.000003 $/tok = 3 $/MTok Note: A pool without supports_cache bills cache reads and writes as ordinary input - it is recommended to avoid such pools for agentic workloads.
native_dialect is the wire format a model is served in natively: one of ant_messages, oai_completions or oai_responses, or null when it declares none. You may call any model through any of our endpoints and we translate, so this is informational: sending a model the dialect it is native to avoids that translation. A pool may override it, so the dialect on each pool entry is the one that actually applies when your request routes there.
GET /_konata/balance
Your live balance and the calling token’s remaining budget (if applicable):
{
"balance": "12345678900",
"token": {
"id": "0190a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"name": "cli",
"usage_limit": "50000000000",
"remaining": "37654321100"
}
} usage_limit and remaining are null for a token without a spend limit. id is the token’s stable identifier, the one usage rows record as metadata.tokenId.
GET /_konata/transactions
Your transaction history, in reverse chronological order.
{
"items": [
{
"id": "48213",
"created_at": "2026-09-06T12:34:56.789Z",
"type": "usage", // "usage" | "topup"
"amount": "1234500", // in nanodollars
"metadata": {
"model": "claude-opus-4-8",
"pool": "budget",
"tokenId": "0190a1b2-...",
"tokenName": "cli",
"usage": { "input": 1200, "output": 300, "cache_read": 8000 },
"tag": "nightly-eval"
}
}
],
"next_cursor": "48213" // null = no more pages
} Optional query parameters:
| parameter | description |
|---|---|
limit | rows per page, 1 to 100 (default 25) |
cursor | the next_cursor of the previous page |
order | desc (default, newest first) or asc |
model | Model name to filter data for |
token | ID of the API token to filter data for |
Errors
| status | meaning |
|---|---|
400 | malformed request body (e.g. no model, or an unregistered one) |
401 | missing or invalid token |
402 | balance exhausted, or this token’s spend limit reached |
403 | account frozen |
404 | unrecognized path |
413 | the request body is too large (current limit is 128 MiB – contact us if you hit this often) |
408 | the request body took too long to deliver |
429 | rate limited - Retry-After says when to come back. Contact us if you hit this often |
502 / 503 / 504 | no upstream could serve the request, or the gateway is momentarily out of request-buffer capacity - Retry-After says when to come back |
Truncated streams
Once a response has started, konata will not retry it. If an upstream dies mid-stream, the stream is
terminated with an error event in the format you asked for - event: error on /v1/messages, response.failed on /v1/responses, an error chunk on /v1/chat/completions:
{ "type": "error", "error": { "type": "api_error", "message": "upstream error" } } Your SDK surfaces it like any other streaming error, so treat it as a failed turn and retry - the next request lands on a healthy upstream. A non-streaming response has nowhere to put the event, so there a truncated body is the signal.