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

scopecancannot
Inferencecall models, get models listread your balance or transactions
Accountread balance and transactionscall a model, so it can never spend
Inference + accounteverything 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.

endpointdialect
POST /v1/messagesAnthropic Messages
POST /v1/messages/count_tokensAnthropic token counting
POST /v1/chat/completionsOpenAI Chat Completions
POST /v1/responsesOpenAI 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:

headereffect
x-knt-pool: <pool>explicitly specify a pool to use, ignore preferences, never reroute
x-knt-allow-cheaper: trueallow 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:

parameterdescription
limitrows per page, 1 to 100 (default 25)
cursorthe next_cursor of the previous page
orderdesc (default, newest first) or asc
modelModel name to filter data for
tokenID of the API token to filter data for

Errors

statusmeaning
400malformed request body (e.g. no model, or an unregistered one)
401missing or invalid token
402balance exhausted, or this token’s spend limit reached
403account frozen
404unrecognized path
413the request body is too large (current limit is 128 MiB – contact us if you hit this often)
408the request body took too long to deliver
429rate limited - Retry-After says when to come back. Contact us if you hit this often
502 / 503 / 504no 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.