API reference

Tokun supports three API formats on one metered pipeline: the OpenAI Chat Completions and Responses APIs, and the Anthropic Messages API. Use the one your client already supports. Model IDs, billing, and routing work the same way on all three.

Base URL and authentication

Authenticate every request with a Tokun API key (starts with sk-) from the API keys page. The base URL depends on the API format you call:

API formatBase URLAuth header
OpenAI (Chat Completions, Responses)https://api.tokun.sh/v1Authorization: Bearer sk-…
Anthropic (Messages)https://api.tokun.shx-api-key: sk-… (or Authorization: Bearer)
OpenAI (Audio: speech, transcriptions)https://api-direct.tokun.sh/v1Authorization: Bearer sk-…
Tokun (Audio: sound effects)https://api-direct.tokun.sh/v1Authorization: Bearer sk-…
The Anthropic base URL has no /v1 suffix because the Anthropic SDK adds the path itself. Use https://api.tokun.sh for Messages and https://api.tokun.sh/v1 for the OpenAI text endpoints. The audio endpoints use https://api-direct.tokun.sh/v1.

If your client can't set an Authorization header, you can put the key in the URL path instead: POST /v1/{sk-key}/chat/completions.

POST /v1/chat/completions

The OpenAI Chat Completions API. Send a standard OpenAI request body with a Tokun model ID. Set "stream": true to stream tokens over SSE. Responses are byte-compatible with the official OpenAI format.

bash
curl https://api.tokun.sh/v1/chat/completions \
  -H "Authorization: Bearer $TOKUN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-opus-4-8",
    "messages": [{"role": "user", "content": "Say hello in one word."}]
  }'

POST /v1/responses

The OpenAI Responses API, with the same authentication and model IDs as Chat Completions. Use it if your SDK targets the Responses API. It is stateless: store is always false, previous_response_id is rejected, and GET /v1/responses/{id} returns 404. Use the Anthropic Messages API if you need signed thinking blocks to round-trip across turns.

Compaction

Both of OpenAI's compaction methods work on every model whose api_surfaces list openai_responses, and on pools through such members. Server-side compaction: send context_management with a compact_threshold on POST /v1/responses, and pass the compaction item it returns back in your next input. Standalone compaction: POST /v1/responses/compact returns a compacted window ("object": "response.compaction") to use as the start of your next input. Compaction is billed like any Responses call, at the model's per-token prices from the response's usage. Standalone compaction takes no max_output_tokens, so it also needs Tokun to know the model's maximum output or context window: the amount held on your balance during the call covers the maximum output, or the context window when no maximum output is known, and the unused part is released when the call settles. If a provider answers without a valid usage block (missing or malformed), Tokun tries the next one, or returns a 502 and charges nothing. A retryable 503 means the model's limits, price or pool members are unavailable at the moment. Any other model returns a 400 invalid_request_error.

bash
curl https://api.tokun.sh/v1/responses/compact \
  -H "Authorization: Bearer $TOKUN_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "openai/gpt-5.5",
    "input": [{"role": "user", "content": "Plan a trip to Kyoto."}]
  }'

POST /v1/messages

The Anthropic Messages API, used by the Anthropic SDKs and Claude Code. Buffered or streaming with the official Anthropic event protocol; signed thinking blocks round-trip in multi-turn tool use. Authenticate with x-api-key: sk-… or Authorization: Bearer.

bash
curl https://api.tokun.sh/v1/messages \
  -H "x-api-key: $TOKUN_API_KEY" \
  -H "anthropic-version: 2023-06-01" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "anthropic/claude-opus-4-8",
    "max_tokens": 64,
    "messages": [{"role": "user", "content": "Say hello in one word."}]
  }'

POST /v1/messages/count_tokens

Returns a conservative token estimate for a Messages request. It is free: no charge and no balance hold.

POST /v1/audio/speech

Text to speech with ElevenLabs models, on the OpenAI audio.speech API. Set voice to an ElevenLabs default voice ID. The response is the whole audio file; response_format is mp3, opus, wav or pcm, and the Content-Type matches it.

python
from openai import OpenAI

client = OpenAI(base_url="https://api-direct.tokun.sh/v1", api_key="sk-…")

audio = client.audio.speech.create(
    model="elevenlabs/eleven_flash_v2_5",
    voice="JBFqnCBsd6RMkjVDRZzb",  # an ElevenLabs voice ID
    input="Thanks for calling. How can I help?",
    response_format="mp3",
    extra_body={"stability": 0.5, "similarity_boost": 0.75, "seed": 42},
)
audio.write_to_file("reply.mp3")

ElevenLabs options go in extra_body: stability, similarity_boost, style and use_speaker_boost (sent as ElevenLabs' voice_settings), seed, previous_text, next_text and language_code. Any other field returns 400 unsupported_parameter. pronunciation_dictionary_locators is refused because it changes the billed character count.

Billing

Priced per 1K characters. You are charged for the characters ElevenLabs bills for the request, at the model's price when you sent it. Before forwarding, Tokun holds an amount for the UTF-8 byte length of input, which is never less than the character count, and releases the unused part when the request settles.

A disconnected client is billed in full: if your connection drops, Tokun keeps reading the response from ElevenLabs and charges for the whole result. Content is not stored: Tokun keeps no generated audio, uploaded audio or transcripts, so there is nothing to fetch again later. A request that fails upstream is not charged and is not retried automatically. The error arrives in the OpenAI error shape, with ElevenLabs' status code and its detail.code as error.code. Every audio response carries an x-tokun-request-id header; quote it when you contact support.

POST /v1/audio/transcriptions

Speech to text with ElevenLabs Scribe, on the OpenAI audio.transcriptions API. Upload one file of up to 25 MB as multipart form data: mp3, wav, m4a, ogg/opus, flac or webm. A larger file returns 413.

python
from openai import OpenAI

client = OpenAI(base_url="https://api-direct.tokun.sh/v1", api_key="sk-…")

with open("call.mp3", "rb") as f:
    transcript = client.audio.transcriptions.create(
        model="elevenlabs/scribe_v2",
        file=f,
        extra_body={"language_code": "en", "diarize": True},
    )
print(transcript.text)

ElevenLabs options go in extra_body: language_code, diarize, num_speakers, timestamps_granularity, tag_audio_events and use_multi_channel. These return 400: keyterms, additional_formats, and the asynchronous options webhook, webhook_id, webhook_metadata, cloud_storage_url and source_url.

Billing

Priced per hour of audio. You are charged for the audio duration ElevenLabs reports, silence included, at the model's price when you sent it. With use_multi_channel set to true, each channel is billed for the full duration. Before forwarding, Tokun holds an amount for the duration it reads from the file plus 5%, capped at 10 hours, and releases the unused part when the request settles. A transcript with no words, such as a silent file, is still a completed transcription and is charged.

One request has 300 seconds in total, covering the upload, the transcription and the response. If a timeout is reached, the error you see comes from whichever layer reached its limit first.
A disconnected client is billed in full: if your connection drops, Tokun keeps reading the response from ElevenLabs and charges for the whole result. Content is not stored: Tokun keeps no generated audio, uploaded audio or transcripts, so there is nothing to fetch again later. A request that fails upstream is not charged and is not retried automatically. The error arrives in the OpenAI error shape, with ElevenLabs' status code and its detail.code as error.code. Every audio response carries an x-tokun-request-id header; quote it when you contact support.

Sound effects

Sound effects use POST /v1/audio/generate with a sound-effects model. This is Tokun's own API, so call it over plain HTTP: the OpenAI SDKs have no method for it. input.prompt describes the sound, and input.duration_seconds is required, from 0.5 to 30 seconds. output.format is optional: mp3, opus, wav or pcm. Sound effects are returned whole. A request without input.duration_seconds, outside its range, or with any other field returns 400. A sound-effects model on /v1/audio/speech returns 400 model_type_mismatch.

python
import requests

response = requests.post(
    "https://api-direct.tokun.sh/v1/audio/generate",
    headers={"Authorization": "Bearer sk-…"},
    json={
        "model": "elevenlabs/eleven_text_to_sound_v2",
        "input": {"prompt": "Rain on a tin roof, distant thunder", "duration_seconds": 5},
        "output": {"format": "mp3"},
    },
)
response.raise_for_status()
with open("rain.mp3", "wb") as audio:
    audio.write(response.content)

Billing

A sound-effects model is priced either per second of generated audio or per generation; its listed price says which. Per second, you are charged for input.duration_seconds rounded up to the millisecond: 0.5001 seconds is billed as 501 ms. Per generation, each request is charged once, however long the effect is. The amount held before forwarding is the same as the charge.

A disconnected client is billed in full: if your connection drops, Tokun keeps reading the response from ElevenLabs and charges for the whole result. Content is not stored: Tokun keeps no generated audio, uploaded audio or transcripts, so there is nothing to fetch again later. A request that fails upstream is not charged and is not retried automatically. The error arrives in the OpenAI error shape, with ElevenLabs' status code and its detail.code as error.code. Every audio response carries an x-tokun-request-id header; quote it when you contact support.

GET /v1/models

Official model pools

Official model pools appear in the model catalog and use a pool ID such as flash-pool. Choose an available pool from the live catalog; pool names and members are configured by Tokun administrators.

When the pool enables member selection, use pool-id/member-name, replacing the model's lab prefix with the pool ID. The selected member is the only model that can execute; the pool's pricing mode still applies. When selection is disabled, this form returns 400.

bash
# Set TOKUN_API_KEY and TOKUN_POOL_ID from your account and the live catalog.
curl "${TOKUN_BASE_URL:-https://api.tokun.sh/v1}/chat/completions" \
  -H "Authorization: Bearer $TOKUN_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"model\":\"$TOKUN_POOL_ID\",\"messages\":[{\"role\":\"user\",\"content\":\"Hello\"}],\"max_tokens\":64}"
bash
# The pool must enable member selection. TOKUN_MEMBER is the member name without its lab prefix.
curl "${TOKUN_BASE_URL:-https://api.tokun.sh/v1}/chat/completions" \
  -H "Authorization: Bearer $TOKUN_API_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"model\":\"$TOKUN_POOL_ID/$TOKUN_MEMBER\",\"messages\":[{\"role\":\"user\",\"content\":\"Hello\"}],\"max_tokens\":64}"

Every pool response echoes the model ID you sent: the pool ID for an automatic request, and pool-id/member-name when you selected a member. The canonical ID of the member that served the request travels in the tokun-resolved-model header, and in executed_public_model on the usage record. Your bill follows the configured pool pricing mode.

A pool stays listed when every member is unavailable. Calling it returns 503; requests never leave the pool to find another model.

Pool api_surfaces lists the APIs its members serve natively, the same meaning a model's api_surfaces has. A pool takes a request on any API where a member would take it when called directly, and each member serves it the way a direct call to that member would, natively or over the same bridge. A request with prompt caching, extended thinking or another Anthropic-native feature that no member serves natively returns 400 model_group_surface_unsupported. Context and output limits are the minimum curated values across members; unknown limits are omitted. Pool count_tokens returns the tokenizer count of the first member in routing order with a native Anthropic upstream, without a charge; when no member has one, it returns the same local estimate a model returns.

Lists the available model IDs and each model's context window. The response format follows the request: a caller that sends x-api-key or anthropic-version (as Anthropic clients do) gets Anthropic's list format; every other caller gets OpenAI's list format. Listing is free (no charge, no hold).

Every entry carries tokun.model_type: chat for a text model, or speech, transcription or sound_effects for an audio model. Chat Completions, Responses and Messages accept chat models only, and a model pool holds chat models only.

Errors

StatusMeaningFix
401Missing or invalid API keySend a valid Tokun sk- key.
402Your account balance cannot cover the requestTop up on the Billing page.
403The account has funds, but this key is over its own budget capSend the request with a key that has no budget cap, or create a key with a higher cap.
404/v1 added to the Anthropic base URLUse https://api.tokun.sh (no /v1) for Messages.
400 — model_not_foundModel ID not recognizedUse a model or pool ID from the live catalog. An unknown ID does not select another model.
400 — model_group_disabledThe model pool is disabledChoose another model or pool from the live catalog.
400 — model_group_member_choice_disabledThe pool does not allow choosing a memberCall the pool ID and let the pool choose the member.
400 — model_group_member_unknownNo member of the pool has that nameUse pool-id/member-name with a member the pool lists.
400 — model_group_member_invalidThe member part of pool-id/member-name is empty or keeps its lab prefixWrite the member's name without its lab prefix, for example flash-pool/deepseek-v4-flash.
400 — model_type_mismatchThe model is an audio model, and Chat Completions, Responses and Messages serve text models onlyCall a model whose tokun.model_type in GET /v1/models is chat.
400 — model_group_surface_unsupportedThe request uses prompt caching, extended thinking or another Anthropic-native feature, and no pool member serves Messages nativelySend it without those features, call one of the pool's APIs, or use another model.
400 — model_type_mismatchThe model does not serve this API: an audio model on a text API, or a speech model on transcriptionsCall the API the model's type lists: speech on /v1/audio/speech, transcription on /v1/audio/transcriptions, sound effects on /v1/audio/generate.
400 — unsupported_parameterAn audio request carries a field Tokun does not pass to ElevenLabsRemove the field; each audio section lists the fields it accepts.
400 — voice_not_foundThe speech voice ID is not one Tokun can serveUse an ElevenLabs default voice ID.
408 — request_timeoutA transcription upload took so long that none of the 300 seconds a transcription has end to end was left to transcribe it; nothing is chargedUpload from a faster connection, or send a smaller or compressed file.
413A transcription upload is larger than 25 MBSplit or compress the file.
503 — model_group_unavailableNo member of the pool can serve right nowRetry with backoff.
503 — provider_config_loadingThe model's provider was added moments ago and its routing configuration is still loadingRetry after the number of seconds in the Retry-After header.
429 / 5xxRate limit or provider errorPassed through from the provider that served the request. Retry with backoff.
402 and 403 are different conditions. 402 is the account balance: the message is insufficient balance. 403 is the per-key budget cap: the message is credential budget exceeded. Tokun checks the balance first, so an empty account returns 402 even when the key is also over its cap. On Messages the same two statuses arrive in the Anthropic envelope, typed billing_error and permission_error.
A row named by a code carries that code in error.code on every API. The 400s are typed invalid_request_error and are not worth retrying: Chat Completions and Responses answer in the OpenAI error shape with param set to model, and Messages in the Anthropic error shape. A pool that does not show its members never names a member in these messages.
Individual models use their token rates. Model pools use one of three modes: fixed pool token rates for every member, the serving member's frozen token rates, or a fixed price per request. Token prices are quoted in USD per 1M tokens; per-request prices are quoted in USD per request. Before forwarding, Tokun places a balance hold for the estimated maximum cost. Token-priced requests settle from the serving model's reported usage: an individual model at its published token rates, a model pool according to its pricing mode. Fixed per-request pools charge once after an upstream success response begins, even if usage is missing, the response is partial or the client disconnects. Failures before success release the hold; unsuccessful retries add no per-request charge.