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 format | Base URL | Auth header |
|---|---|---|
OpenAI (Chat Completions, Responses) | https://api.tokun.sh/v1 | Authorization: Bearer sk-… |
Anthropic (Messages) | https://api.tokun.sh | x-api-key: sk-… (or Authorization: Bearer) |
OpenAI (Audio: speech, transcriptions) | https://api-direct.tokun.sh/v1 | Authorization: Bearer sk-… |
Tokun (Audio: sound effects) | https://api-direct.tokun.sh/v1 | Authorization: Bearer sk-… |
/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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
# 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}"# 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
| Status | Meaning | Fix |
|---|---|---|
401 | Missing or invalid API key | Send a valid Tokun sk- key. |
402 | Your account balance cannot cover the request | Top up on the Billing page. |
403 | The account has funds, but this key is over its own budget cap | Send 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 URL | Use https://api.tokun.sh (no /v1) for Messages. |
400 — model_not_found | Model ID not recognized | Use a model or pool ID from the live catalog. An unknown ID does not select another model. |
400 — model_group_disabled | The model pool is disabled | Choose another model or pool from the live catalog. |
400 — model_group_member_choice_disabled | The pool does not allow choosing a member | Call the pool ID and let the pool choose the member. |
400 — model_group_member_unknown | No member of the pool has that name | Use pool-id/member-name with a member the pool lists. |
400 — model_group_member_invalid | The member part of pool-id/member-name is empty or keeps its lab prefix | Write the member's name without its lab prefix, for example flash-pool/deepseek-v4-flash. |
400 — model_type_mismatch | The model is an audio model, and Chat Completions, Responses and Messages serve text models only | Call a model whose tokun.model_type in GET /v1/models is chat. |
400 — model_group_surface_unsupported | The request uses prompt caching, extended thinking or another Anthropic-native feature, and no pool member serves Messages natively | Send it without those features, call one of the pool's APIs, or use another model. |
400 — model_type_mismatch | The model does not serve this API: an audio model on a text API, or a speech model on transcriptions | Call 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_parameter | An audio request carries a field Tokun does not pass to ElevenLabs | Remove the field; each audio section lists the fields it accepts. |
400 — voice_not_found | The speech voice ID is not one Tokun can serve | Use an ElevenLabs default voice ID. |
408 — request_timeout | A transcription upload took so long that none of the 300 seconds a transcription has end to end was left to transcribe it; nothing is charged | Upload from a faster connection, or send a smaller or compressed file. |
413 | A transcription upload is larger than 25 MB | Split or compress the file. |
503 — model_group_unavailable | No member of the pool can serve right now | Retry with backoff. |
503 — provider_config_loading | The model's provider was added moments ago and its routing configuration is still loading | Retry after the number of seconds in the Retry-After header. |
429 / 5xx | Rate limit or provider error | Passed through from the provider that served the request. Retry with backoff. |
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.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.