Chat completions

POST/v1/chat/completions

The main text route: an OpenAI-shaped conversation.

Request

modelstringbodyrequired
the logical model name from GET /v1/models; a provider prefix is folded away
messagesarraybodyrequired
the conversation in OpenAI form: an array of {role, content}; goes upstream unchanged
streambooleanbody
true — the answer arrives as SSE frames; the gateway's own refusals happen before the first byte
stream_optionsobjectbody
{"include_usage": true} — the only way to get the cost while streaming
max_tokensintegerbody
the output ceiling (max_completion_tokens too); the worst-case price is built on it

Responses

Response

{
  "id": "chatcmpl-…",
  "object": "chat.completion",
  "model": "gpt-5.6-sol",
  "choices": [
    { "index": 0, "message": { "role": "assistant", "content": "Hello!" }, "finish_reason": "stop" }
  ],
  "usage": { "prompt_tokens": 9, "completion_tokens": 12, "cost_usd": "0.0000465" }
}

Stream

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"content":"Hel"}}]}

data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[],"usage":{"prompt_tokens":9,"completion_tokens":12,"cost_usd":"0.0000465"}}

data: [DONE]

moderation stopped the prompt before the model (type: content_policy_violation)

no key in the request, or the key is not ours

the balance does not cover this request's worst-case price

model_not_found — the model is switched off by the admin — it leaves GET /v1/models too

the upstream answered with a rate limit; retry with a delay

the upstream is unreachable (type: upstream_error)

a timeout waiting for the upstream: 300 s read, 60 s without bytes on a stream

Details

Needs a key in Authorization: Bearer or x-api-key.

The gateway forwards the request to an OpenAI-compatible upstream. Only the fields it looks at itself are listed; every other body field (tools, tool_choice, response_format, temperature, seed, logprobs and the rest) is passed through untouched — their meaning is the OpenAI schema's, not ours.

The output ceiling (max_tokens or max_completion_tokens) sets the worst-case price the balance check works from: the bigger it is, the likelier a 402 on a thin balance — lowering it is a valid answer to a refusal. Name neither and the ceiling comes from the model's catalog row, so the request is still not priced at zero.

This request's cost arrives inside the answer: the x-teamtoken-cost-usd header and usage.cost_usd in the body. The value is a decimal string ("0.0000465"), not a number: a number would be re-displayed by the client language's own float rules (Python would show 4.65e-05), while a string reaches your code exactly as written. The cost can also fail to arrive at all — then it is in neither the header nor the field: on a non-streaming answer when the upstream did not report it, on a stream when the model has no catalog tariff. On a stream the headers leave before the cost is knowable, so it is written into the final usage frame — ask for it with "stream_options": {"include_usage": true}. Without include_usage the stream is relayed byte-for-byte with no frame parsing at all, and carries no cost.

A byte-identical request with the same key inside a short TTL (60 s by default) does not reach the model twice — the gateway returns the first answer's body and charges nothing for the second. Only a successful answer of at most 256 KB is cached; anything else goes upstream again. A replay from the cache carries no x-teamtoken-cost-usd header (nothing was charged), and the cost_usd in its body belongs to the first, paid answer. Hence the corollary: an already-paid answer is served even on an empty wallet — the balance check sits AFTER idempotency. The order of pre-flight checks: disabled model → moderation → balance. An answer with no substance at all (no text, no reasoning, no refusal, no tool call) is treated as a failure and retried before it is handed to you, provided there is somewhere left to switch to; an answer cut short by policy or by the output ceiling does not count as empty, and a stream is excluded from the rule entirely, because the 200 has already started. Every error arrives in one envelope (the code field is not on every status): { "error": { "message": "...", "type": "...", "code": "PROVIDER_CODE" } }.

Moderation, when it is enabled, reads the request text and blocks only on a real verdict: a moderator that is down or erroring lets the request through. The answer always carries back the logical model name you asked for. If it does not fit the gateway's wait (300 s read by default, 60 s without bytes on a stream) you get a 504; the gateway marks that non-streaming attempt, and if the upstream finished and billed anyway, the reconciler credits the amount back to your balance as a compensating grant. Upstream host names are scrubbed out of error bodies, so an error's text can differ from what the upstream sent.

This path has no route of its own: one gateway handler takes every POST /v1/*, and its list of accepted paths is closed — any other /v1/* answers 404.

Code examples
curl https://api.teamtoken.store/v1/chat/completions \
  -H "Authorization: Bearer sk-…" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "gpt-5.6-sol",
    "messages": [{ "role": "user", "content": "Hello" }]
  }'
from openai import OpenAI

client = OpenAI(api_key="sk-…", base_url="https://api.teamtoken.store/v1")

stream = client.chat.completions.create(
    model="gpt-5.6-sol",
    messages=[{"role": "user", "content": "Hello"}],
    stream=True,
    stream_options={"include_usage": True},   # no include_usage, no cost in the stream
)
for chunk in stream:
    if chunk.choices and chunk.choices[0].delta.content:
        print(chunk.choices[0].delta.content, end="")
    if chunk.usage:                           # the final frame
        print("cost_usd:", chunk.usage.model_extra["cost_usd"])
Request
https://api.teamtoken.store/v1

The panel calls this domain; in your own code use the address above.

The key is never stored: it lives in this tab until you reload the page.

the logical model name from GET /v1/models; a provider prefix is folded away

the conversation in OpenAI form: an array of {role, content}; goes upstream unchanged

true — the answer arrives as SSE frames; the gateway's own refusals happen before the first byte

{"include_usage": true} — the only way to get the cost while streaming

the output ceiling (max_completion_tokens too); the worst-case price is built on it

This request really goes out and costs money at the model's tariff.

Response

Press “Send request” above and the answer shows up here.