API reference
The API is OpenAI-compatible. If your code already talks to OpenAI, changing the base URL and the key is the whole migration. Your base URL is shown in the console alongside your keys.
Quickstart
Create a key from the console, then point any OpenAI client at the API:
from openai import OpenAI
client = OpenAI(
base_url="https://tokenkey.in/v1",
api_key="tk_live_...",
)
response = client.chat.completions.create(
model="tk-auto",
messages=[{"role": "user", "content": "hello"}],
)Authentication
Pass the key as a bearer token. Keys look like tk_live_<id>_<secret>.
Authorization: Bearer tk_live_...
Only a hash of the secret is stored, so a key is shown once at creation and cannot be recovered afterwards. If you lose one, revoke it and create another. Keys are issued from the console rather than through the API: a key that can mint keys means revoking a leaked one does not revoke the key it already created.
Models
| Model | Purpose |
|---|---|
| tk-auto | The router chooses a tier, and looks the question up on the web first when it needs current information. Recommended default. Any chat request can ask for a search with "web_search": true. |
| tk-base | Fast tier, for short high-volume requests. |
| tk-reason | Reasoning tier: thinks before it answers. Worth the wait for maths, logic and multi-step problems; slower than tk-base for everything else. |
| tk-coder | Code generation and repository-scale edits. Coming shortly — not yet serving requests. |
TokenKey answers in English. Ask in another language and your question is still answered, in English, with a line saying other languages are not supported yet. Translation itself is declined rather than attempted: it is not reliable on this deployment yet.
Naming a model explicitly always wins — the router never second-guesses an explicit choice. Which tier served a request, and why, is recorded and visible in the console.
The available models can change. Call GET /v1/models for the authoritative list.
Using TokenKey in AI clients
Desktop chat apps, editor extensions and terminal agents mostly speak the OpenAI API, so they work here unchanged. Whatever the client calls its fields, it is asking for the same three values:
| Base URL | https://tokenkey.in/v1 |
| API key | tk_live_... |
| Model | tk-auto, tk-base or tk-reason |
Some clients ask for a base URL that already ends in /v1 and others append it themselves. If a connection test fails, that is the first thing to check. Three worked examples:
Chatbox
Desktop and web
- Settings, bottom left, then Model Provider and Add.
- Choose API type OpenAI API Compatible, then fill in the name
TokenKey, your key, and the API hosthttps://tokenkey.in/v1. Leave the API path at its default of/chat/completions. - Under Model, choose New, add
tk-base, and save. - Press Check. It should report a successful connection.
- Pick TokenKey from the model dropdown in a chat.
Cline
VS Code extension
- Open the Cline panel and its settings icon.
- Set API Provider to OpenAI Compatible.
- Base URL
https://tokenkey.in/v1, your key, and model IDtk-base— ortk-autoortk-reason.
opencode
Terminal agent
- Declare the provider in
~/.config/opencode/opencode.jsonc:opencode.jsonc{ "$schema": "https://opencode.ai/config.json", "provider": { "tokenkey": { "npm": "@ai-sdk/openai-compatible", "name": "TokenKey", "options": { "baseURL": "https://tokenkey.in/v1" }, "models": { "tk-auto": { "name": "TokenKey auto" }, "tk-base": { "name": "TokenKey fast" }, "tk-reason": { "name": "TokenKey reasoning" } } } } } - Add the key from inside opencode:
/connect, then Other, provider idtokenkey, then paste it. - Choose the model with
/models, then TokenKey, thentk-auto. - Send a test message. Restart opencode if you edited the config after it was already running.
Agent tools that run for a long time are better pointed at tk-base than tk-auto: the router may send a hard question to the reasoning tier, which thinks before it answers and is slower per step.
Streaming
Set stream: true for server-sent events. Token usage is captured whether or not you ask for it — if you do not set stream_options.include_usage, the gateway requests it internally and strips the extra chunk before it reaches you, so you receive exactly the stream you asked for.
stream = client.chat.completions.create(
model="tk-auto",
messages=[{"role": "user", "content": "explain gradient descent"}],
stream=True,
)
for chunk in stream:
print(chunk.choices[0].delta.content or "", end="")Disconnecting mid-stream cancels the generation upstream rather than leaving it running, and what was consumed up to that point is still recorded.
Rate limits
Two limits apply: requests per minute, enforced on arrival, and tokens per minute plus a daily cap, reconciled after each response. A request’s cost is not known until it finishes, so one call may overshoot the token window and the next is refused rather than the current one being cut off.
Every response carries the current state:
x-ratelimit-limit-requests: 1000 x-ratelimit-remaining-requests: 987 x-ratelimit-reset-requests: 60
A 429 includes Retry-After in seconds. Your exact limits are on the settings page in the console.
Errors
Errors use the OpenAI shape, so SDK error handling works unchanged.
{
"error": {
"message": "The model 'gpt-4o' does not exist. Available models: ...",
"type": "invalid_request_error",
"param": "model",
"code": "model_not_found"
}
}| Status | Code | Meaning |
|---|---|---|
| 401 | invalid_api_key | Missing, malformed, revoked or expired key. |
| 404 | model_not_found | The model is not in the registry. The message lists what is. |
| 400 | tier_unavailable | The tier this request needs is not available — sending images to a text-only service, for example. |
| 429 | rate_limit_exceeded | Request or token quota exceeded. Honour the Retry-After header. |
| 502 | api_error | The inference backend could not be reached. |
Data handling
Prompt and completion text is not stored. Usage records hold token counts, latency, the model requested and the tier that served it — nothing else. Inference runs on hardware in Amaravati, Andhra Pradesh, India, so your text is not forwarded to a model provider abroad.
This is asserted by a test that sends a marker string through streamed and non-streamed completions and then scans every column of every table for it. It runs on every commit, which is how the guarantee survives future changes.