Token Streamer gives you one OpenAI-compatible API for the models you connect. Send requests to https://router.streamtoken.dev/v1 with an API key.
Authentication
Send your key as a Bearer token on every request:
curl https://router.streamtoken.dev/v1/models \
-H "Authorization: Bearer $STREAMTOKEN_API_KEY"A missing, expired or revoked key returns HTTP 401 invalid_api_key. A key can use only the models it was given, in the region it was issued for.
Use the OpenAI SDK
Point any OpenAI SDK at the base URL and use your Token Streamer key.
from openai import OpenAI
client = OpenAI(
base_url="https://router.streamtoken.dev/v1",
api_key="<STREAMTOKEN_API_KEY>",
)
completion = client.chat.completions.create(
model="openai/byok/gpt-5.4-mini",
messages=[{"role": "user", "content": "Reply with a greeting."}],
)
print(completion.choices[0].message.content)import OpenAI from 'openai';
const client = new OpenAI({
baseURL: 'https://router.streamtoken.dev/v1',
apiKey: process.env.STREAMTOKEN_API_KEY,
});
const completion = await client.chat.completions.create({
model: 'openai/byok/gpt-5.4-mini',
messages: [{ role: 'user', content: 'Reply with a greeting.' }],
});
console.log(completion.choices[0].message.content);List models and providers
GET /v1/models lists the models your key can use. GET /v1/providers lists the providers behind them. Results are limited to your key's permitted models, your saved provider connections and the key's region. Neither call starts model inference.
curl https://router.streamtoken.dev/v1/providers \
-H "Authorization: Bearer $STREAMTOKEN_API_KEY"This example key permits one OpenAI model in Europe:
{
"object": "list",
"region": "europe",
"checked_at": null,
"data": [
{
"id": "openai",
"object": "provider",
"name": "OpenAI",
"model_count": 1,
"models": ["openai/byok/gpt-5.4-mini"],
"provider_status": "unknown"
}
]
}Provider status comes from the provider's public status feed. An open incident that mentions the provider's API makes it degraded, or unavailable for a full or major outage. Providers without a public feed (Mistral, xAI, Google) are unknown, and so is any status older than 15 minutes.
List one provider's models:
curl https://router.streamtoken.dev/v1/providers/openai/models \
-H "Authorization: Bearer $STREAMTOKEN_API_KEY"{
"object": "list",
"data": [
{
"id": "openai/byok/gpt-5.4-mini",
"object": "model",
"created": 0,
"owned_by": "openai"
}
]
}These identifiers can be passed to chat completions. A provider outside your key's permitted routes returns HTTP 404 with the provider_not_found error code. Other HTTP methods return HTTP 405. Responses are not cached. The Providers page shows request counts and timing for each provider.
Model identifiers and billing
Identifiers containing /byok/ use your own provider connection: the provider bills your account, and the response header X-StreamToken-Billing-Mode is byok. Token Streamer records measured tokens and an estimate from its reference rates, with a Token Streamer charge of zero. Estimates exclude cache adjustments and account-specific discounts.
Identifiers without /byok/, such as openai/gpt-5.4-mini, use credits from your organization wallet. You do not supply a provider key for them. If your balance is too low, the request returns HTTP 402 (see errors).
Send a completion
POST /v1/chat/completions. Save this request as chat-request.json:
{
"model": "openai/gpt-5.4-mini",
"messages": [
{
"role": "user",
"content": "Reply with a greeting."
}
],
"max_completion_tokens": 64,
"stream": false
}curl https://router.streamtoken.dev/v1/chat/completions \
-H "Authorization: Bearer $STREAMTOKEN_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: completion-example-0001' \
--data-binary @chat-request.jsonThe response looks like this. Text, IDs, timestamps and token counts vary.
{
"id": "chatcmpl-fixture",
"object": "chat.completion",
"created": 1,
"model": "openai/gpt-5.4-mini",
"choices": [
{
"index": 0,
"finish_reason": "stop",
"message": {
"role": "assistant",
"content": "Fixture provider response"
}
}
],
"usage": {
"prompt_tokens": 10,
"completion_tokens": 2,
"total_tokens": 12
}
}Usage is settled at the price version locked before the request is sent. X-StreamToken-Accounting is settled or sync_pending for a non-streaming completion. A repeated Idempotency-Key returns 409 request_exists and does not call the provider again.
Stream a completion
Set "stream": true. Save this request as stream-request.json:
{
"model": "openai/gpt-5.4-mini",
"messages": [
{
"role": "user",
"content": "Reply with a greeting."
}
],
"max_completion_tokens": 64,
"stream": true,
"stream_options": {
"include_usage": true
}
}curl --no-buffer https://router.streamtoken.dev/v1/chat/completions \
-H "Authorization: Bearer $STREAMTOKEN_API_KEY" \
-H 'Content-Type: application/json' \
-H 'Idempotency-Key: streaming-example-0001' \
--data-binary @stream-request.jsonThe response has content type text/event-stream. Events arrive separated by blank lines. One network read can hold part of an event or several events, so parse the SSE framing before parsing JSON.
data: {"id":"chatcmpl-stream-fixture","object":"chat.completion.chunk","created":1,"model":"openai/gpt-5.4-mini","choices":[{"index":0,"delta":{"role":"assistant","content":""},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-fixture","object":"chat.completion.chunk","created":1,"model":"openai/gpt-5.4-mini","choices":[{"index":0,"delta":{"content":"Hello 🌍"},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-fixture","object":"chat.completion.chunk","created":1,"model":"openai/gpt-5.4-mini","choices":[{"index":0,"delta":{},"finish_reason":"stop"}]}
data: {"id":"chatcmpl-stream-fixture","object":"chat.completion.chunk","created":1,"model":"openai/gpt-5.4-mini","choices":[],"usage":{"prompt_tokens":10,"completion_tokens":2,"total_tokens":12}}
data: [DONE]stream_options.include_usage: true adds a final chunk with empty choices and the measured usage. The stream ends with data: [DONE] only after a valid finish and valid final usage.
Handle interruptions
Errors before streaming starts return a JSON error and a non-success status. A provider rejection returns HTTP 502 provider_rejected and, when the provider answered, a numeric error.provider_status such as 401, 403 or 429. Raw provider errors and credentials are never forwarded.
Once streaming starts, the HTTP status cannot change. If the provider stream is interrupted, the router sends a data: {"error": ...} event (customer-owned routes use stream_interrupted) and closes the stream without [DONE]. A client that disconnects can miss that event. Never treat the end of a connection alone as success. The router does not retry inference, switch providers or guess a charge. Use the X-Request-Id header to look up the outcome in Diagnostics.
Streams wait up to 10 seconds for provider headers and 10 seconds for each read, and last at most 60 seconds.
Receive function calls
Function tools work with stream: true. The router forwards your tool definitions and streams the model's proposed calls. Your application authorizes and runs the tool; the router does not.
{
"model": "openai/gpt-5.4-mini",
"messages": [
{
"role": "user",
"content": "Reply with a greeting."
}
],
"max_completion_tokens": 64,
"stream": true,
"stream_options": {
"include_usage": true
},
"tools": [
{
"type": "function",
"function": {
"name": "weather",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string"
}
},
"required": ["city"],
"additionalProperties": false
},
"strict": true
}
}
],
"tool_choice": "required"
}The model streams argument fragments like these:
data: {"id":"chatcmpl-stream-fixture","object":"chat.completion.chunk","created":1,"model":"openai/gpt-5.4-mini","choices":[{"index":0,"delta":{"role":"assistant","tool_calls":[{"index":0,"id":"call_weather","type":"function","function":{"name":"weather","arguments":""}}]},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-fixture","object":"chat.completion.chunk","created":1,"model":"openai/gpt-5.4-mini","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"{\"city\":"}}]},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-fixture","object":"chat.completion.chunk","created":1,"model":"openai/gpt-5.4-mini","choices":[{"index":0,"delta":{"tool_calls":[{"index":0,"function":{"arguments":"\"Paris\"}"}}]},"finish_reason":null}]}
data: {"id":"chatcmpl-stream-fixture","object":"chat.completion.chunk","created":1,"model":"openai/gpt-5.4-mini","choices":[{"index":0,"delta":{},"finish_reason":"tool_calls"}]}
data: {"id":"chatcmpl-stream-fixture","object":"chat.completion.chunk","created":1,"model":"openai/gpt-5.4-mini","choices":[],"usage":{"prompt_tokens":10,"completion_tokens":2,"total_tokens":12}}
data: [DONE]Accumulate function.arguments by tool_calls[index] until the choice finishes. Parse the assembled JSON and validate it against your tool schema before running anything. A strict declaration does not replace your own authorization checks.
For the next turn, send the assistant message with the assembled tool_calls, then a tool message for each call with its tool_call_id and a string result. Every call needs a result before the next request. Use a new idempotency key. OpenAI routes stream function calls; Anthropic routes stream text.
Create embeddings
POST /v1/embeddings turns text into vectors. Fields: model, input (one text, or 1 to 128 non-empty texts of at most 32,768 characters each), encoding_format (float or base64) and dimensions (OpenAI models only). Other fields return invalid_request.
{
"model": "openai/byok/text-embedding-3-small",
"input": ["first text", "second text"]
}The response has object: "list", one data item per input in order, the model ID and usage.prompt_tokens. Chat models are refused here, and embedding models are refused by chat completions, with model_not_found. Request text is never stored.
Classify content
POST /v1/moderations checks text. Fields: model and input (one text, or 1 to 32 non-empty texts of at most 32,768 characters each). Images and other fields return invalid_request.
{ "model": "openai/byok/omni-moderation-latest", "input": "Text to check" }The response has an id, the model ID and one results item per input, in order. Each has flagged, categories and category_scores (0 to 1). Category names come from the provider. Mistral does not return flagged, so the router sets it to true when any category is true. Moderation is free from both providers, so usage rows have no token counts or cost.
Transcribe audio
POST /v1/audio/transcriptions turns audio into text. Send multipart/form-data with a Content-Length header and these fields: model, file (up to 25 MB; mp3, mp4, mpeg, mpga, m4a, wav or webm), and optionally language (a two-letter ISO-639-1 code) and prompt (up to 2,000 characters).
curl https://router.streamtoken.dev/v1/audio/transcriptions \
-H "Authorization: Bearer $STREAMTOKEN_API_KEY" \
-F model=openai/byok/gpt-transcribe -F [email protected]The response is { "text": "...", "model": "...", "usage": { "type": "duration", "seconds": 61.2 } }. usage is null when the provider does not report a duration. The estimate is the per-minute price times the audio length, rounded up. Neither the audio nor the text is stored.
Generate images
POST /v1/images/generations creates images from a prompt. Fields: model, prompt (1 to 32,000 characters), n (1 to 4), size (auto or WIDTHxHEIGHT), quality (low, medium, high, xhigh, max or auto) and output_format (png, jpeg or webp). Image edits, input images and URL results are not supported.
{
"model": "openai/byok/gpt-image-2.6-flare",
"prompt": "A red kite over a beach",
"size": "1024x1024",
"quality": "low"
}The response has created, the model ID, one data item per image with b64_json (and revised_prompt when the provider sends one), and usage with input_tokens and output_tokens. Neither the prompt nor the images are stored. The Test button generates one 1024x1024 low-quality image, which your provider bills to your account.
Generate videos
POST /v1/videos/generations creates one video with sound from a prompt. Fields: model, prompt (1 to 32,000 characters), seconds (3 to 15, default 10), resolution (360p, 720p or 1080p, default 720p) and aspect_ratio (16:9 or 9:16). Input images, reference videos, editing, extension and 4K are not supported.
{
"model": "google/byok/gemini-omni-1.2-flash",
"prompt": "A red kite over a beach",
"seconds": 5,
"resolution": "720p"
}The response has created, the model ID, one data item with b64_json and mime_type (video/mp4), and usage. The call waits until the video is ready, which can take a few minutes; it gives up after 5 minutes. Videos use your own Google Gemini key. The estimate uses $1.50 input and $12 output per 1M tokens; a 10 second 720p clip is about $0.70. The router asks the provider not to store the request. Neither the prompt nor the video is stored by Token Streamer. The Test button generates one 3 second 360p clip, about $0.07, billed to your provider account.
Choose a region
Organization owners choose the provider region in Models. Europe supports the enabled OpenAI and Mistral routes. The United States also supports the Claude 4.6-and-later routes and the supported xAI routes. Availability depends on the model, your account and the regional endpoint. A missing regional credential or unavailable model stops the request before it is sent. This setting covers the provider path only.
Customer-owned routes accept up to four inline base64 images and a request body up to 512 KiB.
Find an alternative model
GET /v1/model-availability lists the customer-owned routes your key can use. Each result has id, provider, capabilities, provider_status and observed_status, plus the region and the snapshot's checked_at time. Missing or expired snapshots return unknown.
Each result also declares context_tokens, max_output_tokens (the model's own output limit), structured_output (false for every route), usage_reporting (true) and processing_location. processing_location is verified when the provider reports where inference ran and the router checks it (Claude in the United States), unknown when it cannot check, and unsupported when the route is not available in the region.
Structured output is not supported: a request with response_format returns HTTP 400 unsupported_parameter with param: "response_format".
To find alternatives to a model, pass model and optionally a comma-separated requires list (image_input, streaming, function_tools). Routes that are degraded or unavailable are left out. An unknown status is not a guarantee. You choose the model for your next request.
Fall back to other models
On a customer-owned route, send X-StreamToken-Fallback-Models with one to three distinct alternative model IDs, in order. For example: X-StreamToken-Fallback-Models: anthropic/byok/claude-haiku-4.5, openai/byok/gpt-5.6-luna.
When fresh health data marks the requested route unavailable, the router uses the first alternative that is healthy and allowed in your region. Routes without function tools are skipped for requests that use tools. If no alternative qualifies, the request returns 503 model_unavailable before any provider is called. If health data is unknown or expired, the requested model is used.
A successful switch sets X-StreamToken-Selected-Model to the model used. The response and the Usage record name it too. Check the alternatives' rates first. The router makes one attempt and does not retry on a different model if the attempt fails or the stream is interrupted.
Supported parameters
| Field | Behavior |
|---|---|
model |
Required. Use an identifier permitted for your key. |
messages |
1-100 messages. Text roles: system, developer, user, assistant. Streaming also accepts assistant function-call history and matching tool results. |
max_completion_tokens |
Optional integer, from 1 up to the model's own output limit (max_output_tokens in GET /v1/model-availability). A larger value returns HTTP 400. max_tokens is accepted as an alias. Send one field. |
stream |
Optional boolean, default false. |
stream_options |
Streaming only. The only setting is boolean include_usage. |
tools |
Streaming only. 1-16 uniquely named function tools with object parameters, optional description and boolean strict. |
tool_choice |
With tools: auto, none, required, or a named declared function. |
The JSON request body is limited to 64 KiB on credit-funded routes. Function names use 1-64 ASCII letters, digits, underscores or hyphens. Call IDs are at most 128 characters and argument strings at most 32,768 characters. Multiple choices, sampling settings and the Responses API are not supported. Unsupported settings are rejected, not ignored. This is a subset of the OpenAI API, not complete compatibility.
Errors
An insufficient-credit request returns HTTP 402:
{
"error": {
"message": "There are not enough test credits for the maximum request reservation.",
"type": "invalid_request_error",
"param": null,
"code": "insufficient_credits"
}
}| Result | Meaning |
|---|---|
400 invalid_request / unsupported_parameter |
Fix the request and resend. Nothing was sent to a provider. |
401 invalid_api_key |
Use an active key. |
403 billing_review_required |
New requests are paused while billing is reviewed. Contact support. |
404 model_not_found |
The model is not available for this key. |
409 request_exists |
You already used this idempotency key. The provider was not called again, and the original is not replayed. |
502 provider_rejected |
The provider rejected the request. No usage was charged. |
502 reconciliation_pending or an SSE error |
The outcome is uncertain, and credits stay held until it is resolved. Look the request up in Diagnostics. |
503 accounting_unavailable |
The charge could not be reserved, so the request was not sent. Retry. |
Useful response headers:
X-Request-Ididentifies the HTTP request and changes on each retry.X-StreamToken-Operation-Ididentifies the credit operation. Look it up in Diagnostics.X-StreamToken-Price-Versionidentifies the rates locked for the request.
Token Streamer records the outcome, whether the request streamed, its duration and first-output timing. It does not receive your prompts, completions, function arguments or provider credentials in these reports. Held credits are marked Needs review until resolved. See credits for balances and recovery.
Data retention
Owners and administrators can download request metadata from Usage and provider key activity from Models. Each export is limited to 10,000 records and leaves out credentials, prompts and replies. The retention setting in Usage applies to both. Older records are hidden at once and later removed. See data controls.