Invitation-only AI inference
Tokenaro
An AI server with OpenAI- and Anthropic-compatible APIs. Six models cover chat, coding, reasoning, image input, document OCR, speech-to-text, text-to-speech and real-time voice, all at one address. Using them takes a personal API key from the server's owner; this page shows how to connect once you have one.
https://api.tokenaro.ai/v1
Use it anywhere an app asks for an “OpenAI base URL”. Anthropic-format clients take https://api.tokenaro.ai, without /v1. Send your key as Authorization: Bearer <key> or x-api-key: <key>.
Paste the key you were given. It is placed into every command, settings snippet and agent prompt on this page. It stays in this browser tab: it is not saved, and this page never sends it anywhere.
No key entered. Commands and prompts show YOUR_TOKENARO_KEY instead.
01 · Getting started
Three steps
-
Get your key
There is no sign-up: the server's owner gives each person their own API key, so ask the person who invited you. Each key has its own model list and limits and can be revoked, so don't share yours. Paste it into the key box above, and the commands below will use it.
-
Check that it works
Save the key in an environment variable once. The examples on this page read it from there.
export TOKENARO_API_KEY="YOUR_TOKENARO_KEY" curl https://api.tokenaro.ai/v1/models -H "Authorization: Bearer $TOKENARO_API_KEY"$env:TOKENARO_API_KEY = "YOUR_TOKENARO_KEY" curl.exe https://api.tokenaro.ai/v1/models -H "Authorization: Bearer $env:TOKENARO_API_KEY"The reply lists the models your key can use.
-
Connect your app or agent
In any OpenAI- or Anthropic-compatible app, enter the base URL, your key and a model id. For an AI agent, open Agent setup: each agent has numbered steps to do it yourself, and a prompt you can paste into the agent's chat instead, so it changes its own settings, tests the connection and reports back.
02 · Models
Six models, one API
Each model runs on its own service, and that service runs nothing else. Choose the model with the request's model field.
| Model id | What it's for | In → out | Context | Endpoint |
|---|---|---|---|---|
qwen3.8-27bmain |
The main model, and your default choice. Strong at general chat, coding, tool calls and image input. It is a reasoning model. | text, image → text | 262K | /v1/chat/completions (streaming) |
deepseek-v4.1-flashslow |
A very large model that runs on CPU. It gives the best answers on hard questions, but it is slow. Use it when quality matters more than waiting. It is a reasoning model, so stream it. | text → text | 1M | /v1/chat/completions |
lfm3-audio-2bfast |
A small, very fast model that handles audio natively. Use it for quick helper jobs (titles, short summaries, classification) and for voice. It supports tool calls, but it is not for agentic coding. | text, audio → text, speech | 128K | /v1/chat/completions, wss://…/v1/realtime |
glm-ocr |
Document OCR. Send a page image and get back the text as Markdown, including tables and formulas. | image → text | 128K | /v1/chat/completions |
granite-speech-5.0-2b |
Speech-to-text (transcription). | audio → text | 16K | /v1/audio/transcriptions |
chatterbox-turbo |
Text-to-speech with natural English and one built-in voice. The voice field is accepted but ignored. |
text → speech | 8K | /v1/audio/speech |
qwen3.8-27b.lfm3-audio-2b for short, simple jobs.deepseek-v4.1-flash, stream the reply and be patient.Not available: embeddings, image generation, fine-tuning, and the files, batches and assistants APIs.
03 · Endpoints
Endpoints and examples
Tokenaro speaks two formats with the same keys and models: OpenAI's (every endpoint below except /v1/messages) and Anthropic's Messages API. The official SDKs work once you change the base URL: https://api.tokenaro.ai/v1 for OpenAI, https://api.tokenaro.ai for Anthropic. Send your key as Authorization: Bearer <key> or x-api-key: <key>. The examples read it from TOKENARO_API_KEY (see step 2).
| Endpoint | Models | Notes |
|---|---|---|
GET /v1/models | — | Lists the models your key may use. |
POST /v1/chat/completions | qwen3.8-27b, deepseek-v4.1-flash, lfm3-audio-2b, glm-ocr | messages, max_tokens, stream, tools. Content parts: image_url (qwen, glm-ocr) and input_audio (lfm3). |
POST /v1/completions | qwen3.8-27b, deepseek-v4.1-flash, lfm3-audio-2b | OpenAI's older Completions API: a plain prompt instead of messages. |
POST /v1/responses | qwen3.8-27b, deepseek-v4.1-flash, lfm3-audio-2b | OpenAI's Responses API (input, max_output_tokens, stream), for clients such as Codex that use it instead of Chat Completions. |
POST /v1/messagesPOST /v1/messages/count_tokens | qwen3.8-27b, deepseek-v4.1-flash, lfm3-audio-2b | Anthropic's Messages API (streaming, tool use), for Claude Code and the Anthropic SDKs. Their base URL is https://api.tokenaro.ai, without /v1. |
POST /v1/audio/transcriptions | granite-speech-5.0-2b | Multipart form with the audio in field file. response_format: json or verbose_json. |
POST /v1/audio/speech | chatterbox-turbo | input up to 4,096 characters. response_format: mp3 (default), opus, aac, flac, wav or pcm. speed: 0.25–4. |
WSS /v1/realtime?model=lfm3-audio-2b | lfm3-audio-2b | Real-time voice over WebSocket using the OpenAI Realtime (GA) protocol. Send the key in the Authorization: Bearer header. |
Examples
A normal chat request. Reasoning models return their thinking in reasoning_content and the answer in content. Give max_tokens some headroom.
curl https://api.tokenaro.ai/v1/chat/completions \
-H "Authorization: Bearer $TOKENARO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "qwen3.8-27b",
"max_tokens": 2048,
"messages": [{"role": "user", "content": "Give me three names for a grey cat."}]
}'
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.tokenaro.ai/v1",
api_key=os.environ["TOKENARO_API_KEY"])
r = client.chat.completions.create(
model="qwen3.8-27b",
max_tokens=2048, # reasoning model: leave room for its thinking
messages=[{"role": "user", "content": "Give me three names for a grey cat."}],
)
msg = r.choices[0].message
print(msg.content)
# The thinking, if you want it: getattr(msg, "reasoning_content", None)
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.tokenaro.ai/v1",
apiKey: process.env.TOKENARO_API_KEY,
});
const r = await client.chat.completions.create({
model: "qwen3.8-27b",
max_tokens: 2048, // reasoning model: leave room for its thinking
messages: [{ role: "user", content: "Give me three names for a grey cat." }],
});
console.log(r.choices[0].message.content);
// The thinking, if you want it: r.choices[0].message.reasoning_content
Streaming sends tokens as they are generated. Use it for anything long, and always for deepseek-v4.1-flash: Cloudflare cuts off a request that sends nothing for 100 seconds. Thinking arrives first, as reasoning_content deltas.
curl -N https://api.tokenaro.ai/v1/chat/completions \
-H "Authorization: Bearer $TOKENARO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "deepseek-v4.1-flash",
"stream": true,
"max_tokens": 8192,
"messages": [{"role": "user", "content": "Prove that there are infinitely many primes."}]
}'
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.tokenaro.ai/v1",
api_key=os.environ["TOKENARO_API_KEY"])
stream = client.chat.completions.create(
model="deepseek-v4.1-flash",
stream=True,
max_tokens=8192,
messages=[{"role": "user", "content": "Prove that there are infinitely many primes."}],
)
for chunk in stream:
if not chunk.choices:
continue
delta = chunk.choices[0].delta
if delta.content: # the answer
print(delta.content, end="", flush=True)
# thinking arrives first in getattr(delta, "reasoning_content", None)
print()
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.tokenaro.ai/v1",
apiKey: process.env.TOKENARO_API_KEY,
});
const stream = await client.chat.completions.create({
model: "deepseek-v4.1-flash",
stream: true,
max_tokens: 8192,
messages: [{ role: "user", content: "Prove that there are infinitely many primes." }],
});
for await (const chunk of stream) {
const delta = chunk.choices[0]?.delta;
if (delta?.content) process.stdout.write(delta.content); // the answer
// thinking arrives first in delta.reasoning_content
}
Send images as image_url content parts, either as a data: URI or an https URL. For OCR, use glm-ocr with the text Text Recognition: (or Table Recognition:, Formula Recognition:). To ask a question about a photo, use the same request shape with qwen3.8-27b and put your question in the text part. PDFs must become images first, e.g. pdftoppm -r 150 -png doc.pdf page.
IMG=$(base64 -w0 page-1.png) # macOS: base64 -i page-1.png
curl https://api.tokenaro.ai/v1/chat/completions \
-H "Authorization: Bearer $TOKENARO_API_KEY" \
-H "Content-Type: application/json" \
-d @- <<EOF
{"model": "glm-ocr", "max_tokens": 4096,
"messages": [{"role": "user", "content": [
{"type": "image_url", "image_url": {"url": "data:image/png;base64,$IMG"}},
{"type": "text", "text": "Text Recognition:"}]}]}
EOF
import base64, os
from openai import OpenAI
client = OpenAI(base_url="https://api.tokenaro.ai/v1",
api_key=os.environ["TOKENARO_API_KEY"])
b64 = base64.b64encode(open("page-1.png", "rb").read()).decode()
r = client.chat.completions.create(
model="glm-ocr", # or "qwen3.8-27b" with a question
max_tokens=4096,
messages=[{"role": "user", "content": [
{"type": "image_url", "image_url": {"url": f"data:image/png;base64,{b64}"}},
{"type": "text", "text": "Text Recognition:"},
]}],
)
print(r.choices[0].message.content) # the page as Markdown
import fs from "node:fs";
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.tokenaro.ai/v1",
apiKey: process.env.TOKENARO_API_KEY,
});
const b64 = fs.readFileSync("page-1.png").toString("base64");
const r = await client.chat.completions.create({
model: "glm-ocr", // or "qwen3.8-27b" with a question
max_tokens: 4096,
messages: [{ role: "user", content: [
{ type: "image_url", image_url: { url: `data:image/png;base64,${b64}` } },
{ type: "text", text: "Text Recognition:" },
] }],
});
console.log(r.choices[0].message.content); // the page as Markdown
Upload an audio file as multipart form data. response_format can be json (the default, {"text": …}) or verbose_json (adds duration and language).
curl https://api.tokenaro.ai/v1/audio/transcriptions \
-H "Authorization: Bearer $TOKENARO_API_KEY" \
-F model=granite-speech-5.0-2b \
-F [email protected] \
-F response_format=json
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.tokenaro.ai/v1",
api_key=os.environ["TOKENARO_API_KEY"])
with open("clip.wav", "rb") as f:
t = client.audio.transcriptions.create(model="granite-speech-5.0-2b", file=f)
print(t.text)
import fs from "node:fs";
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.tokenaro.ai/v1",
apiKey: process.env.TOKENARO_API_KEY,
});
const t = await client.audio.transcriptions.create({
model: "granite-speech-5.0-2b",
file: fs.createReadStream("clip.wav"),
});
console.log(t.text);
Send text and get audio back. input can be up to 4,096 characters, so split longer text. Write numbers and abbreviations as words for the best reading, and strip Markdown first, because it is read out literally. The SDKs require a voice. Any value works, because the server ignores it.
curl https://api.tokenaro.ai/v1/audio/speech \
-H "Authorization: Bearer $TOKENARO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model": "chatterbox-turbo", "voice": "alloy",
"input": "Hello! This is Tokenaro speaking.",
"response_format": "mp3"}' \
-o hello.mp3
import os
from openai import OpenAI
client = OpenAI(base_url="https://api.tokenaro.ai/v1",
api_key=os.environ["TOKENARO_API_KEY"])
with client.audio.speech.with_streaming_response.create(
model="chatterbox-turbo",
voice="alloy", # required by the SDK, ignored by the server
input="Hello! This is Tokenaro speaking.",
response_format="mp3",
) as r:
r.stream_to_file("hello.mp3")
import fs from "node:fs";
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.tokenaro.ai/v1",
apiKey: process.env.TOKENARO_API_KEY,
});
const r = await client.audio.speech.create({
model: "chatterbox-turbo",
voice: "alloy", // required by the SDK, ignored by the server
input: "Hello! This is Tokenaro speaking.",
response_format: "mp3",
});
fs.writeFileSync("hello.mp3", Buffer.from(await r.arrayBuffer()));
For a spoken conversation with lfm3-audio-2b, open a WebSocket to wss://api.tokenaro.ai/v1/realtime?model=lfm3-audio-2b with the same Bearer header. It uses the OpenAI Realtime (GA) protocol:
- send
session.updatewith{"type": "realtime", "output_modalities": ["audio"]} - send
input_audio_buffer.appendwith base64 PCM16, mono, 24 kHz - send
input_audio_buffer.commit, thenresponse.create - receive
response.output_audio.deltaevents (base64 PCM16) untilresponse.done
Browsers can't add an Authorization header to a WebSocket. Connect from your own server or app, and never put your key in a web page.
# https://github.com/vi/websocat — type one JSON event per line
websocat -H "Authorization: Bearer $TOKENARO_API_KEY" \
"wss://api.tokenaro.ai/v1/realtime?model=lfm3-audio-2b"
{"type": "session.update", "session": {"type": "realtime", "output_modalities": ["audio"]}}
import asyncio, base64, json, os, wave
import websockets
URL = "wss://api.tokenaro.ai/v1/realtime?model=lfm3-audio-2b"
HEADERS = {"Authorization": f"Bearer {os.environ['TOKENARO_API_KEY']}"}
async def main():
# websockets < 14: use extra_headers= instead of additional_headers=
async with websockets.connect(URL, additional_headers=HEADERS) as ws:
send = lambda ev: ws.send(json.dumps(ev))
await send({"type": "session.update",
"session": {"type": "realtime", "output_modalities": ["audio"]}})
with wave.open("question.wav") as w: # PCM16, mono, 24 kHz
pcm = w.readframes(w.getnframes())
await send({"type": "input_audio_buffer.append",
"audio": base64.b64encode(pcm).decode()})
await send({"type": "input_audio_buffer.commit"})
await send({"type": "response.create"})
audio = bytearray()
async for message in ws:
ev = json.loads(message)
if ev["type"] == "response.output_audio.delta":
audio += base64.b64decode(ev["delta"])
elif ev["type"] == "error":
print(ev); break
elif ev["type"] == "response.done":
break
with wave.open("answer.wav", "wb") as w:
w.setnchannels(1); w.setsampwidth(2); w.setframerate(24000)
w.writeframes(bytes(audio))
asyncio.run(main())
import fs from "node:fs";
import WebSocket from "ws";
const ws = new WebSocket("wss://api.tokenaro.ai/v1/realtime?model=lfm3-audio-2b", {
headers: { Authorization: `Bearer ${process.env.TOKENARO_API_KEY}` },
});
const send = (ev) => ws.send(JSON.stringify(ev));
// raw PCM16, mono, 24 kHz: ffmpeg -i question.wav -ar 24000 -ac 1 -f s16le question.pcm
const pcm = fs.readFileSync("question.pcm");
const out = [];
ws.on("open", () => {
send({ type: "session.update", session: { type: "realtime", output_modalities: ["audio"] } });
send({ type: "input_audio_buffer.append", audio: pcm.toString("base64") });
send({ type: "input_audio_buffer.commit" });
send({ type: "response.create" });
});
ws.on("message", (data) => {
const ev = JSON.parse(data);
if (ev.type === "response.output_audio.delta") out.push(Buffer.from(ev.delta, "base64"));
if (ev.type === "error") console.error(ev.error);
if (ev.type === "response.done" || ev.type === "error") {
fs.writeFileSync("answer.pcm", Buffer.concat(out)); // PCM16, mono, 24 kHz
ws.close();
}
});
04 · Agent setup
Connect your AI agent
Each tab gives you two ways. Follow the numbered steps to set it up yourself, or copy the prompt into your agent's chat: the agent backs up its config, adds Tokenaro as a provider, tests each model and reports what it changed and how to undo it. Enter your key first, and the snippets and prompts will contain it.
Pasting a prompt that contains your key puts the key in that agent's chat history. If you'd rather not, set it up yourself, or leave the key box empty: the agent will then ask you to put the key into its secrets file yourself.
For Hermes Agent by Nous Research. Main model and vision go to qwen3.8-27b, short helper jobs to lfm3-audio-2b, voice input to granite-speech-5.0-2b and voice output to chatterbox-turbo.
Set it up yourself
-
Back up your settings:
cp ~/.hermes/config.yaml ~/.hermes/config.yaml.bak-tokenaro [ -f ~/.hermes/.env ] && cp ~/.hermes/.env ~/.hermes/.env.bak-tokenaro -
Add these lines to
~/.hermes/.env(create it if needed, thenchmod 600 ~/.hermes/.env). The last three are for voice. IfVOICE_TOOLS_OPENAI_KEYalready holds a real OpenAI key, this moves your voice features to Tokenaro.TOKENARO_API_KEY=YOUR_TOKENARO_KEY VOICE_TOOLS_OPENAI_KEY=YOUR_TOKENARO_KEY STT_OPENAI_BASE_URL=https://api.tokenaro.ai/v1 STT_OPENAI_MODEL=granite-speech-5.0-2b -
In
~/.hermes/config.yaml, set these blocks. Keep the other keys already in them, and leave the other auxiliary tasks alone. Key names differ between Hermes versions, so check yours withhermes config check.model: provider: custom base_url: https://api.tokenaro.ai/v1 key_env: TOKENARO_API_KEY default: qwen3.8-27b context_length: 262144 auxiliary: vision: # images: must be qwen3.8-27b base_url: https://api.tokenaro.ai/v1 api_key: ${TOKENARO_API_KEY} model: qwen3.8-27b compression: # long input: needs the big context base_url: https://api.tokenaro.ai/v1 api_key: ${TOKENARO_API_KEY} model: qwen3.8-27b session_search: # long input base_url: https://api.tokenaro.ai/v1 api_key: ${TOKENARO_API_KEY} model: qwen3.8-27b title_generation: # quick text helper base_url: https://api.tokenaro.ai/v1 api_key: ${TOKENARO_API_KEY} model: lfm3-audio-2b web_extract: # quick text helper base_url: https://api.tokenaro.ai/v1 api_key: ${TOKENARO_API_KEY} model: lfm3-audio-2b -
For voice, also set these blocks in the same file:
stt: enabled: true provider: openai streaming: false # no live partial transcripts on Tokenaro openai: model: granite-speech-5.0-2b tts: provider: openai openai: base_url: https://api.tokenaro.ai/v1 model: chatterbox-turbo voice: alloy # ignored: there is one built-in voice -
Restart Hermes (and
hermes gateway, if you run it) and send it a message;qwen3.8-27bnow answers. With voice set up, try/voice on,/voice tts, then ask “What's two plus two?”.
Undo: cp ~/.hermes/config.yaml.bak-tokenaro ~/.hermes/config.yaml, the same for ~/.hermes/.env, then restart Hermes. Moving from the old “Darknight” setup (Tailscale IP, separate ports, local TTS shim)? The prompt below also removes those leftovers.
Or let your agent do it
Paste this into Hermes's chat. It makes the same changes, tests every model, and reports back.
# Task: connect yourself (Hermes Agent) to Tokenaro
Tokenaro is a private, OpenAI-compatible AI server. It replaces the old "Darknight" server: no Tailscale IP, no separate ports, no local TTS shim. Everything now goes through one HTTPS address with a personal API key. Reconfigure yourself to use it, test it, and report back. Work through the steps in order.
## Connection
- Base URL: https://api.tokenaro.ai/v1 (all models and endpoints)
- API key: YOUR_TOKENARO_KEY
- Auth header: "Authorization: Bearer <key>" ("x-api-key: <key>" also works). HTTP 401 = wrong or revoked key. HTTP 403 = no key sent (blocked at the edge) or a model not on this key. HTTP 429 = this key's limits were hit (simultaneous requests or requests/minute): wait and retry.
## Models (exact ids)
- qwen3.8-27b: your main model AND your vision model. Chat, coding, tool calls, image input. Reasoning model. Context 262,144 tokens.
- lfm3-audio-2b: small, very fast helper for short text jobs (titles, short summaries, classification). Context 128,000 tokens. Not for coding.
- deepseek-v4.1-flash: very large and slow (runs on CPU). Only for hard questions nobody is waiting on. Context 1,048,576 tokens. Never use it for voice or as an auxiliary model.
- granite-speech-5.0-2b: speech-to-text, POST /v1/audio/transcriptions.
- chatterbox-turbo: text-to-speech, POST /v1/audio/speech (OpenAI format; one built-in English voice, "voice" is ignored; mp3 by default).
- glm-ocr: document OCR (page image as an image_url part plus the text "Text Recognition:").
## Rules
1. Back up before editing anything:
cp ~/.hermes/config.yaml ~/.hermes/config.yaml.bak-tokenaro
[ -f ~/.hermes/.env ] && cp ~/.hermes/.env ~/.hermes/.env.bak-tokenaro
2. The key is a secret. Never print or echo it, and never put it in a log, a commit or our chat. It lives only in ~/.hermes/.env; config.yaml refers to it as ${TOKENARO_API_KEY}. When you show me config, mask it (sk-...last 4). If the key under "Connection" still reads YOUR_TOKENARO_KEY, ask me to add it to ~/.hermes/.env myself.
3. Change only what this task names. Key names differ between Hermes versions: before writing YAML, check yours (`hermes config`, `hermes config check`, and cli-config.yaml.example in your install) and adapt the examples below. Don't invent keys.
4. Reasoning models return their thinking in reasoning_content and the answer in content. Leave max_tokens headroom (1024+ even for a one-word answer, 4096+ for real work). Empty content with finish_reason "length" means the budget ran out.
5. A request that sends nothing for 100 s is cut off (HTTP 524), so long generations must stream.
## Step 1: Secrets in ~/.hermes/.env (chmod 600)
TOKENARO_API_KEY=YOUR_TOKENARO_KEY
VOICE_TOOLS_OPENAI_KEY=YOUR_TOKENARO_KEY
STT_OPENAI_BASE_URL=https://api.tokenaro.ai/v1
STT_OPENAI_MODEL=granite-speech-5.0-2b
(VOICE_TOOLS_OPENAI_KEY is the key Hermes's openai STT and TTS providers send. If it currently holds a real OpenAI key, ask me before replacing it.)
## Step 2: Remove the old Darknight setup, if present
grep -rn -E '100\.110\.46\.112|darknight|:808[1-7]|127\.0\.0\.1:8099|k2-horizon|gemma-4-e4b' ~/.hermes/config.yaml ~/.hermes/.env ~/.hermes/skills 2>/dev/null
Every hit is stale: replace it with the settings below, or delete it. The local TTS shim is no longer needed:
systemctl --user disable --now darknight-tts-shim 2>/dev/null
then delete ~/.config/systemd/user/darknight-tts-shim.service and ~/.hermes/darknight_tts_shim.py, and run `systemctl --user daemon-reload`.
## Step 3: ~/.hermes/config.yaml
model:
provider: custom
base_url: https://api.tokenaro.ai/v1
key_env: TOKENARO_API_KEY
default: qwen3.8-27b
context_length: 262144
auxiliary:
vision: # images: must be qwen3.8-27b
base_url: https://api.tokenaro.ai/v1
api_key: ${TOKENARO_API_KEY}
model: qwen3.8-27b
compression: # long input: needs the big context
base_url: https://api.tokenaro.ai/v1
api_key: ${TOKENARO_API_KEY}
model: qwen3.8-27b
session_search: # long input
base_url: https://api.tokenaro.ai/v1
api_key: ${TOKENARO_API_KEY}
model: qwen3.8-27b
title_generation: # quick text helper
base_url: https://api.tokenaro.ai/v1
api_key: ${TOKENARO_API_KEY}
model: lfm3-audio-2b
web_extract: # quick text helper
base_url: https://api.tokenaro.ai/v1
api_key: ${TOKENARO_API_KEY}
model: lfm3-audio-2b
stt:
enabled: true
provider: openai
streaming: false # no live partial transcripts on Tokenaro
openai:
model: granite-speech-5.0-2b
tts:
provider: openai
openai:
base_url: https://api.tokenaro.ai/v1
model: chatterbox-turbo
voice: alloy # ignored: there is one built-in voice
Keep the other keys already in those blocks (timeouts, enabled flags and so on), and leave the other auxiliary tasks alone. If web_extract fails because a page is too long for lfm3-audio-2b's 128K window, move web_extract to qwen3.8-27b.
## Step 4: Test one call per model (the key is read from .env, never shown)
export TOKENARO_API_KEY="$(grep -m1 '^TOKENARO_API_KEY=' ~/.hermes/.env | cut -d= -f2-)"
U=https://api.tokenaro.ai/v1; H="Authorization: Bearer $TOKENARO_API_KEY"
# a) the key works; prints the models it may use
curl -sS "$U/models" -H "$H" | python3 -c 'import json,sys; print([m["id"] for m in json.load(sys.stdin)["data"]])'
# b) chat with both text models (expect "OK" twice)
for M in qwen3.8-27b lfm3-audio-2b; do
curl -sS "$U/chat/completions" -H "$H" -H 'Content-Type: application/json' \
-d "{\"model\":\"$M\",\"max_tokens\":1024,\"messages\":[{\"role\":\"user\",\"content\":\"Reply with just OK\"}]}" \
| python3 -c 'import json,sys; r=json.load(sys.stdin); print(r["choices"][0]["message"]["content"] if "choices" in r else r)'
done
# c) text-to-speech (expect an MP3 file)
curl -sS "$U/audio/speech" -H "$H" -H 'Content-Type: application/json' \
-d '{"model":"chatterbox-turbo","voice":"alloy","input":"Hello from Tokenaro."}' -o /tmp/tokenaro-test.mp3 && file /tmp/tokenaro-test.mp3
# d) speech-to-text of that file (expect about "Hello from Tokenaro.")
curl -sS "$U/audio/transcriptions" -H "$H" -F model=granite-speech-5.0-2b -F file=@/tmp/tokenaro-test.mp3 -F response_format=json
Then restart Hermes (and `hermes gateway`, if you run it) so the new config loads. Answer one message on the new setup. If voice is enabled, also do a spoken round trip: /voice on, /voice tts, then ask "What's two plus two?".
## Step 5: Report back
- What you changed: files, config sections, .env variable names (key masked), and the stale Darknight entries you removed.
- Every test: pass or fail, with a short excerpt of the output or the error.
- Anything that needed me (sudo, a decision about an existing key).
## How to undo
cp ~/.hermes/config.yaml.bak-tokenaro ~/.hermes/config.yaml
cp ~/.hermes/.env.bak-tokenaro ~/.hermes/.env # if you backed it up
Then restart Hermes.
Checked against the Hermes Agent docs (configuration, providers) on 2026-10-29.
For OpenClaw, the open-source personal AI agent formerly called Clawdbot and Moltbot. Tokenaro goes under models.providers in ~/.openclaw/openclaw.json, with qwen3.8-27b as the primary model and lfm3-audio-2b as the utility model. Voice setup is optional.
Set it up yourself
-
Back up your settings (if your config lives elsewhere, for example via
OPENCLAW_CONFIG_PATH, back up that file):cp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.bak-tokenaro [ -f ~/.openclaw/.env ] && cp ~/.openclaw/.env ~/.openclaw/.env.bak-tokenaro -
Add this line to
~/.openclaw/.env(create it if needed, thenchmod 600 ~/.openclaw/.env):TOKENARO_API_KEY=YOUR_TOKENARO_KEY -
Merge this into
~/.openclaw/openclaw.json(JSON5) without replacing the existing objects. Note your currentprimaryandutilityModelfirst, so you can switch back. If a model allowlist (agents.defaults.modelPolicy.allow) is set, add"tokenaro/*"to it.{ models: { providers: { tokenaro: { baseUrl: "https://api.tokenaro.ai/v1", apiKey: "${TOKENARO_API_KEY}", api: "openai-completions", timeoutSeconds: 600, models: [ { id: "qwen3.8-27b", name: "Qwen3.8 27B (Tokenaro)", reasoning: true, input: ["text", "image"], contextWindow: 262144, maxTokens: 32768 }, { id: "lfm3-audio-2b", name: "LFM3 Audio 2B (Tokenaro, fast)", reasoning: false, input: ["text"], contextWindow: 128000, maxTokens: 4096 }, { id: "deepseek-v4.1-flash", name: "DeepSeek V4.1 Flash (Tokenaro, slow)", reasoning: true, input: ["text"], contextWindow: 1048576, maxTokens: 32768 }, ], }, }, }, agents: { defaults: { model: { primary: "tokenaro/qwen3.8-27b" }, // keep any existing fallbacks utilityModel: "tokenaro/lfm3-audio-2b", // titles, recaps, other short internal jobs }, }, } -
Check and test. The gateway reloads
openclaw.jsonby itself; if the models don't show up, runopenclaw gateway restart. The last command is a full turn through OpenClaw (its name may differ in your version).openclaw doctor # must validate without errors openclaw models list | grep tokenaro # the three models openclaw agent --message "Reply with just OK"Existing chats stay on their old model: start a new one, or send
/model default. For voice, see step 3 of the prompt below.
Undo: cp ~/.openclaw/openclaw.json.bak-tokenaro ~/.openclaw/openclaw.json, restore ~/.openclaw/.env the same way (or delete its TOKENARO_API_KEY line), run openclaw gateway restart, and send /model default in any chat still on a Tokenaro model.
Or let your agent do it
Paste this into OpenClaw's chat. It makes the same changes, tests every model, and reports back.
# Task: connect yourself (OpenClaw) to Tokenaro
Tokenaro is a private, OpenAI-compatible AI server. Add it to your configuration as a custom OpenAI-compatible model provider and make its main model your default. Then test it and report back. Work through the steps in order.
## Connection
- Base URL: https://api.tokenaro.ai/v1 (all models and endpoints)
- API key: YOUR_TOKENARO_KEY
- Auth header: "Authorization: Bearer <key>" ("x-api-key: <key>" also works). HTTP 401 = wrong or revoked key. HTTP 403 = no key sent (blocked at the edge) or a model not on this key. HTTP 429 = this key's limits were hit (simultaneous requests or requests/minute): wait and retry.
## Models (exact ids)
- qwen3.8-27b: main model. Chat, coding, tool calls, image input. Reasoning model. Context 262,144 tokens.
- lfm3-audio-2b: small, very fast helper (titles, short summaries, classification, quick voice replies). Context 128,000 tokens. Not for agentic coding.
- deepseek-v4.1-flash: very large and slow (runs on CPU). Highest quality on hard questions, when someone can wait. Reasoning model. Context 1,048,576 tokens.
- granite-speech-5.0-2b: speech-to-text, POST /v1/audio/transcriptions.
- chatterbox-turbo: text-to-speech, POST /v1/audio/speech (OpenAI format; one built-in English voice, "voice" is ignored; mp3 by default).
- glm-ocr: document OCR (page image as an image_url part plus the text "Text Recognition:").
## Rules
1. Back up before editing anything (if your config lives elsewhere, e.g. OPENCLAW_CONFIG_PATH, back up that file):
cp ~/.openclaw/openclaw.json ~/.openclaw/openclaw.json.bak-tokenaro
[ -f ~/.openclaw/.env ] && cp ~/.openclaw/.env ~/.openclaw/.env.bak-tokenaro
2. The key is a secret. Never print or echo it, and never put it in a log, a commit or our chat. It lives only in ~/.openclaw/.env; openclaw.json refers to it as "${TOKENARO_API_KEY}". When you show me config, mask it (sk-...last 4). If the key under "Connection" still reads YOUR_TOKENARO_KEY, ask me to add it to ~/.openclaw/.env myself.
3. Change only what this task names: no other providers, channels or agents. Config keys change between OpenClaw versions, so check yours first (`openclaw --version`, `openclaw config schema`, https://docs.openclaw.ai/concepts/model-providers/custom-providers). Where your version's docs differ from the example below, follow the docs. Don't invent keys.
4. Reasoning models return their thinking in reasoning_content and the answer in content. Leave max_tokens headroom. A request that sends nothing for 100 s is cut off (HTTP 524), so long generations must stream.
## Step 1: Store the key
Add this line to ~/.openclaw/.env (create the file if needed, chmod 600):
TOKENARO_API_KEY=YOUR_TOKENARO_KEY
## Step 2: Add the provider to ~/.openclaw/openclaw.json (JSON5)
Merge this into the existing objects; don't replace them. Write down the current agents.defaults.model.primary and agents.defaults.utilityModel first, for the report and for undo.
{
models: {
providers: {
tokenaro: {
baseUrl: "https://api.tokenaro.ai/v1",
apiKey: "${TOKENARO_API_KEY}",
api: "openai-completions",
timeoutSeconds: 600,
models: [
{ id: "qwen3.8-27b", name: "Qwen3.8 27B (Tokenaro)", reasoning: true,
input: ["text", "image"], contextWindow: 262144, maxTokens: 32768 },
{ id: "lfm3-audio-2b", name: "LFM3 Audio 2B (Tokenaro, fast)", reasoning: false,
input: ["text"], contextWindow: 128000, maxTokens: 4096 },
{ id: "deepseek-v4.1-flash", name: "DeepSeek V4.1 Flash (Tokenaro, slow)", reasoning: true,
input: ["text"], contextWindow: 1048576, maxTokens: 32768 },
],
},
},
},
agents: {
defaults: {
model: { primary: "tokenaro/qwen3.8-27b" }, // keep any existing fallbacks
utilityModel: "tokenaro/lfm3-audio-2b", // titles, recaps, other short internal jobs
},
},
}
- If agents.defaults.modelPolicy.allow (or an older allowlist) restricts models, add "tokenaro/*".
- If a call fails with HTTP 400 about "reasoning_effort", add compat: { supportsReasoningEffort: false } to that model entry.
- If utility jobs fail because their input is longer than lfm3-audio-2b's 128K window, set utilityModel to "tokenaro/qwen3.8-27b".
- Existing chat sessions stay pinned to their old model. New sessions use the new default; `/model default` switches an existing one.
## Step 3 (optional): voice
Do this only if you already use text-to-speech or voice-note transcription, or I ask for it:
- Text-to-speech: tts.provider "openai", with tts.providers.openai = { apiKey: "${TOKENARO_API_KEY}", baseUrl: "https://api.tokenaro.ai/v1", model: "chatterbox-turbo", speakerVoice: "alloy", responseFormat: "mp3" }. The voice is ignored; there is one built-in voice.
- Transcription: a tools.media.models entry { provider: "openai", model: "granite-speech-5.0-2b", baseUrl: "https://api.tokenaro.ai/v1", capabilities: ["audio"] }, but only if your version lets that entry authenticate with the Tokenaro key (see https://docs.openclaw.ai/nodes/audio). Never send another provider's key (a real OpenAI key, for example) to Tokenaro, and never send the Tokenaro key anywhere else.
## Step 4: Validate and test one call per model
openclaw doctor # the config must validate; fix anything it reports about your edits
openclaw models list | grep tokenaro # the three models should appear
The gateway watches openclaw.json and reloads it. If the change doesn't show up, run `openclaw gateway restart`.
Then test each model (the key is read from .env, never shown):
export TOKENARO_API_KEY="$(grep -m1 '^TOKENARO_API_KEY=' ~/.openclaw/.env | cut -d= -f2-)"
U=https://api.tokenaro.ai/v1; H="Authorization: Bearer $TOKENARO_API_KEY"
curl -sS "$U/models" -H "$H" | python3 -c 'import json,sys; print([m["id"] for m in json.load(sys.stdin)["data"]])'
for M in qwen3.8-27b lfm3-audio-2b; do # expect "OK" twice
curl -sS "$U/chat/completions" -H "$H" -H 'Content-Type: application/json' \
-d "{\"model\":\"$M\",\"max_tokens\":1024,\"messages\":[{\"role\":\"user\",\"content\":\"Reply with just OK\"}]}" \
| python3 -c 'import json,sys; r=json.load(sys.stdin); print(r["choices"][0]["message"]["content"] if "choices" in r else r)'
done
# deepseek-v4.1-flash is slow: stream it and stop after the first chunks (data: lines = pass)
curl -sSN "$U/chat/completions" -H "$H" -H 'Content-Type: application/json' \
-d '{"model":"deepseek-v4.1-flash","stream":true,"max_tokens":512,"messages":[{"role":"user","content":"Reply with just OK"}]}' | head -n 3
If you did step 3, also test speech (POST $U/audio/speech with model chatterbox-turbo, written to a file) and transcription of that file (POST $U/audio/transcriptions, model granite-speech-5.0-2b).
Finally, run one end-to-end turn through yourself, e.g. `openclaw agent --message "Reply with just OK"` (or your version's equivalent), and confirm that it ran on tokenaro/qwen3.8-27b.
## Step 5: Report back
- What you changed (file and keys; key masked), and the previous primary and utility models.
- Every test: pass or fail, with a short excerpt of the output or the error.
- Anything you skipped or that needs me.
## How to undo
cp ~/.openclaw/openclaw.json.bak-tokenaro ~/.openclaw/openclaw.json
cp ~/.openclaw/.env.bak-tokenaro ~/.openclaw/.env # if you backed it up; otherwise remove the TOKENARO_API_KEY line
Then run `openclaw gateway restart`, and `/model default` in any session still on a tokenaro model.
Checked against the OpenClaw docs (custom providers, models, configuration) on 2026-10-29.
For the opencode coding agent. Tokenaro goes into your global opencode.json as an @ai-sdk/openai-compatible provider, with qwen3.8-27b as the model and lfm3-audio-2b as the small model (titles). It works with opencode 1.x and 2.x.
Set it up yourself
-
Back up your global config, if you have one (it may be
opencode.jsoncinstead):cp ~/.config/opencode/opencode.json ~/.config/opencode/opencode.json.bak-tokenaro -
Save the key in a file only you can read:
mkdir -p ~/.config/opencode printf '%s' 'YOUR_TOKENARO_KEY' > ~/.config/opencode/tokenaro.key chmod 600 ~/.config/opencode/tokenaro.keyPrefer an environment variable? Export
TOKENARO_API_KEYin your shell profile and write"{env:TOKENARO_API_KEY}"as theapiKeyin the next step. -
Merge this into
~/.config/opencode/opencode.json(create it if needed) and keep everything else. Note your currentmodelandsmall_modelfirst. A project's ownopencode.jsonwithproviderormodeloverrides this file.{ "$schema": "https://opencode.ai/config.json", "provider": { "tokenaro": { "npm": "@ai-sdk/openai-compatible", "name": "Tokenaro", "options": { "baseURL": "https://api.tokenaro.ai/v1", "apiKey": "{file:~/.config/opencode/tokenaro.key}" }, "models": { "qwen3.8-27b": { "name": "Qwen3.8 27B (Tokenaro)", "tool_call": true, "reasoning": true, "attachment": true, "modalities": { "input": ["text", "image"], "output": ["text"] }, "limit": { "context": 262144, "output": 32768 } }, "deepseek-v4.1-flash": { "name": "DeepSeek V4.1 Flash (Tokenaro, slow)", "tool_call": true, "reasoning": true, "modalities": { "input": ["text"], "output": ["text"] }, "limit": { "context": 1048576, "output": 32768 } }, "lfm3-audio-2b": { "name": "LFM3 Audio 2B (Tokenaro, titles)", "tool_call": true, "modalities": { "input": ["text"], "output": ["text"] }, "limit": { "context": 128000, "output": 4096 } } } } }, "model": "tokenaro/qwen3.8-27b", "small_model": "tokenaro/lfm3-audio-2b" }This V1 format works in opencode 1.x and 2.x. If your file already uses the V2
providerskey, the prompt below lists the V2 field names. -
Test:
opencode models tokenaro # the three models opencode run -m tokenaro/qwen3.8-27b "Reply with just OK" # expect OK
Undo: cp ~/.config/opencode/opencode.json.bak-tokenaro ~/.config/opencode/opencode.json (or remove the tokenaro provider and restore your old model and small_model), then rm ~/.config/opencode/tokenaro.key.
Or let your agent do it
Paste this into opencode. It makes the same changes, tests every model, and reports back.
# Task: connect yourself (opencode) to Tokenaro
Tokenaro is a private, OpenAI-compatible AI server. Add it to your global opencode config as a custom OpenAI-compatible provider and make its main model your default. Then test it and report back. Work through the steps in order.
## Connection
- Base URL: https://api.tokenaro.ai/v1 (OpenAI chat completions: /v1/chat/completions)
- API key: YOUR_TOKENARO_KEY
- Auth header: "Authorization: Bearer <key>" ("x-api-key: <key>" also works). HTTP 401 = wrong or revoked key. HTTP 403 = no key sent (blocked at the edge) or a model not on this key. HTTP 429 = this key's limits were hit (simultaneous requests or requests/minute): wait and retry.
## Models (exact ids)
- qwen3.8-27b: main model. Coding, chat, tool calls, image input. Reasoning model. Context 262,144 tokens.
- deepseek-v4.1-flash: very large and slow (runs on CPU). Highest quality on hard questions, when I can wait. Reasoning model. Context 1,048,576 tokens.
- lfm3-audio-2b: small, very fast. Use it only as the small model (session titles). Context 128,000 tokens. Not for coding.
(Tokenaro also has glm-ocr, granite-speech-5.0-2b and chatterbox-turbo for OCR and voice; opencode doesn't need them.)
## Rules
1. Back up before editing anything. The global config is ~/.config/opencode/opencode.json or opencode.jsonc (create it if neither exists):
cp ~/.config/opencode/opencode.json ~/.config/opencode/opencode.json.bak-tokenaro
If the current project has its own opencode.json with "provider", "providers" or "model", it overrides the global file. Tell me, and don't edit it unless I say so.
2. The key is a secret. Never print or echo it, and never put it in a log, a commit or our chat. It lives only in ~/.config/opencode/tokenaro.key; the config reads it with "{file:~/.config/opencode/tokenaro.key}". When you show me config, mask it (sk-...last 4). If the key under "Connection" still reads YOUR_TOKENARO_KEY, ask me to create that file myself.
3. Change only what this task names: no other providers, agents or permissions. Check `opencode --version` and the provider docs for your version (1.x: https://opencode.ai/docs/providers, 2.x: https://opencode.ai/v2/docs/providers) before editing. Don't invent keys.
4. Reasoning models return their thinking separately from the answer and need max_tokens headroom. A request that sends nothing for 100 s is cut off (HTTP 524).
## Step 1: Store the key
Create ~/.config/opencode/tokenaro.key containing only the key, no trailing newline (permissions 600):
YOUR_TOKENARO_KEY
## Step 2: Add the provider
Merge this into ~/.config/opencode/opencode.json and keep everything else. Write down the current "model" and "small_model" first, for the report and for undo.
{
"$schema": "https://opencode.ai/config.json",
"provider": {
"tokenaro": {
"npm": "@ai-sdk/openai-compatible",
"name": "Tokenaro",
"options": {
"baseURL": "https://api.tokenaro.ai/v1",
"apiKey": "{file:~/.config/opencode/tokenaro.key}"
},
"models": {
"qwen3.8-27b": {
"name": "Qwen3.8 27B (Tokenaro)",
"tool_call": true, "reasoning": true, "attachment": true,
"modalities": { "input": ["text", "image"], "output": ["text"] },
"limit": { "context": 262144, "output": 32768 }
},
"deepseek-v4.1-flash": {
"name": "DeepSeek V4.1 Flash (Tokenaro, slow)",
"tool_call": true, "reasoning": true,
"modalities": { "input": ["text"], "output": ["text"] },
"limit": { "context": 1048576, "output": 32768 }
},
"lfm3-audio-2b": {
"name": "LFM3 Audio 2B (Tokenaro, titles)",
"tool_call": true,
"modalities": { "input": ["text"], "output": ["text"] },
"limit": { "context": 128000, "output": 4096 }
}
}
}
},
"model": "tokenaro/qwen3.8-27b",
"small_model": "tokenaro/lfm3-audio-2b"
}
This is the V1 format, which opencode 1.x and 2.x both accept. If your file already uses the V2 "providers" key, write the same entry in V2 form instead, and keep that entry entirely in one format:
- "package": "@opencode/ai/providers/openai-compatible"
- "settings": { "baseURL": "https://api.tokenaro.ai/v1", "apiKey": "{file:~/.config/opencode/tokenaro.key}" }
- per model: "capabilities": { "tools": true, "input": [...], "output": ["text"] }, plus the same "limit"
- the title model goes in agents.title.model instead of "small_model".
## Step 3: Test one call per model
opencode models tokenaro # the three models should be listed
export TOKENARO_API_KEY="$(cat ~/.config/opencode/tokenaro.key)"
U=https://api.tokenaro.ai/v1; H="Authorization: Bearer $TOKENARO_API_KEY"
curl -sS "$U/models" -H "$H" | python3 -c 'import json,sys; print([m["id"] for m in json.load(sys.stdin)["data"]])'
for M in qwen3.8-27b lfm3-audio-2b; do # expect "OK" twice
curl -sS "$U/chat/completions" -H "$H" -H 'Content-Type: application/json' \
-d "{\"model\":\"$M\",\"max_tokens\":1024,\"messages\":[{\"role\":\"user\",\"content\":\"Reply with just OK\"}]}" \
| python3 -c 'import json,sys; r=json.load(sys.stdin); print(r["choices"][0]["message"]["content"] if "choices" in r else r)'
done
# deepseek-v4.1-flash is slow: stream it and stop after the first chunks (data: lines = pass)
curl -sSN "$U/chat/completions" -H "$H" -H 'Content-Type: application/json' \
-d '{"model":"deepseek-v4.1-flash","stream":true,"max_tokens":512,"messages":[{"role":"user","content":"Reply with just OK"}]}' | head -n 3
Then end to end through opencode itself:
opencode run -m tokenaro/qwen3.8-27b "Reply with just OK"
## Step 4: Report back
- What you changed (file and keys; key masked), and the previous "model" and "small_model".
- Every test: pass or fail, with a short excerpt of the output or the error.
- Anything you skipped or that needs me.
## How to undo
cp ~/.config/opencode/opencode.json.bak-tokenaro ~/.config/opencode/opencode.json
rm ~/.config/opencode/tokenaro.key
(or remove the "tokenaro" provider entry and restore the old "model" and "small_model")
Checked against the opencode docs (providers, V1, providers, V2, config) and the published config schema on 2026-10-29.
For OpenAI's Codex CLI. Tokenaro goes into ~/.codex/config.toml as a custom model provider, and qwen3.8-27b becomes the default only once a test passes.
Current Codex versions talk to providers only through OpenAI's Responses API (/v1/responses), which Tokenaro serves, streaming included. Both ways below still test it before making Tokenaro the default.
Set it up yourself
-
Back up your config (use
$CODEX_HOMEinstead of~/.codexif it is set):cp ~/.codex/config.toml ~/.codex/config.toml.bak-tokenaro -
Put the key in an environment variable. Add this line to
~/.zshrcor~/.bashrc, then open a new terminal:export TOKENARO_API_KEY='YOUR_TOKENARO_KEY'On Windows, run this once, then open a new terminal:
setx TOKENARO_API_KEY "YOUR_TOKENARO_KEY" -
Append this to
~/.codex/config.tomland change nothing else yet:[model_providers.tokenaro] name = "Tokenaro" base_url = "https://api.tokenaro.ai/v1" env_key = "TOKENARO_API_KEY" wire_api = "responses" stream_idle_timeout_ms = 600000 -
Test it (expect
OK):codex exec --skip-git-repo-check -c model_provider=tokenaro -c model=qwen3.8-27b "Reply with just OK" -
If it worked, make it the default. Note your current
model_providerandmodel, then set these at the top ofconfig.toml, before any[table]. They apply from the next Codex session.model_provider = "tokenaro" model = "qwen3.8-27b" model_context_window = 262144
Undo: cp ~/.codex/config.toml.bak-tokenaro ~/.codex/config.toml and remove the TOKENARO_API_KEY line from your shell startup file (on Windows: reg delete HKCU\Environment /v TOKENARO_API_KEY /f).
Or let your agent do it
Paste this into Codex. It makes the same changes, tests the connection first, and reports back.
# Task: connect yourself (Codex CLI) to Tokenaro
Tokenaro is a private, OpenAI-compatible AI server. Add it to your config as a custom model provider and test it. Only if the tests pass, make it your default. Report back. Work through the steps in order.
Heads-up: current Codex versions talk to model providers only through OpenAI's Responses API (wire_api = "responses"), which Tokenaro serves. Step 3 checks it. If it fails, undo your edits and tell me. Don't try to work around it.
## Connection
- Base URL: https://api.tokenaro.ai/v1
- API key: YOUR_TOKENARO_KEY
- Auth header: "Authorization: Bearer <key>" ("x-api-key: <key>" also works). HTTP 401 = wrong or revoked key. HTTP 403 = no key sent (blocked at the edge) or a model not on this key. HTTP 429 = this key's limits were hit (simultaneous requests or requests/minute): wait and retry.
## Model
- qwen3.8-27b: use this one. Coding, tool calls, image input. Reasoning model. Context 262,144 tokens.
- Don't use the other Tokenaro models with Codex: lfm3-audio-2b is too small for agentic coding, and deepseek-v4.1-flash runs on CPU and is far too slow for interactive use.
## Rules
1. Back up before editing anything (use $CODEX_HOME instead of ~/.codex if it is set):
cp ~/.codex/config.toml ~/.codex/config.toml.bak-tokenaro
2. The key is a secret. Never print or echo it, and never put it in a log, a commit or our chat. It lives only in the environment variable TOKENARO_API_KEY; config.toml names the variable (env_key), never the key. When you show me config, mask it (sk-...last 4). If the key under "Connection" still reads YOUR_TOKENARO_KEY, ask me to set the variable myself.
3. Change only what this task names: no other providers, profiles, sandbox or approval settings. Check `codex --version` and https://developers.openai.com/codex/config-reference first, and follow your version's documented keys. Don't invent keys.
4. Your sandbox may block network access. If it does, ask me to approve the test commands instead of skipping them.
## Step 1: Store the key
Add this line to the shell startup file I use (~/.zshrc or ~/.bashrc; on Windows, set a user environment variable instead):
export TOKENARO_API_KEY='YOUR_TOKENARO_KEY'
Codex reads it when it starts, so it takes effect in the next Codex session. For the tests below, load it into your shell without printing it. (If I start Codex from an IDE that doesn't read that file, tell me.)
## Step 2: Add the provider to ~/.codex/config.toml (append it; change nothing else yet)
[model_providers.tokenaro]
name = "Tokenaro"
base_url = "https://api.tokenaro.ai/v1"
env_key = "TOKENARO_API_KEY"
wire_api = "responses"
stream_idle_timeout_ms = 600000
## Step 3: Test
U=https://api.tokenaro.ai/v1; H="Authorization: Bearer $TOKENARO_API_KEY"
# a) the key works; prints the models it may use
curl -sS "$U/models" -H "$H" | python3 -c 'import json,sys; print([m["id"] for m in json.load(sys.stdin)["data"]])'
# b) chat completions (expect "OK")
curl -sS "$U/chat/completions" -H "$H" -H 'Content-Type: application/json' \
-d '{"model":"qwen3.8-27b","max_tokens":1024,"messages":[{"role":"user","content":"Reply with just OK"}]}' \
| python3 -c 'import json,sys; r=json.load(sys.stdin); print(r["choices"][0]["message"]["content"] if "choices" in r else r)'
# c) Responses API, the one Codex needs (pass = a JSON response object with output text; 404/400 = fail)
curl -sS "$U/responses" -H "$H" -H 'Content-Type: application/json' \
-d '{"model":"qwen3.8-27b","max_output_tokens":1024,"input":"Reply with just OK"}' | head -c 800; echo
# d) end to end through Codex
codex exec --skip-git-repo-check -c model_provider=tokenaro -c model=qwen3.8-27b "Reply with just OK"
If c) or d) fails, restore the backup (see "How to undo") and report the error.
## Step 4: Make it the default (only if step 3 passed)
Write down the current top-level model_provider and model. Then set them at the top level of config.toml, before any [table]:
model_provider = "tokenaro"
model = "qwen3.8-27b"
model_context_window = 262144
The change applies from the next Codex session.
## Step 5: Report back
- What you changed (file and keys; key masked), and the previous model_provider and model.
- Every test: pass or fail, with a short excerpt of the output or the error.
- Anything you skipped or that needs me.
## How to undo
cp ~/.codex/config.toml.bak-tokenaro ~/.codex/config.toml
Remove the TOKENARO_API_KEY line from the shell startup file.
(To keep Tokenaro available without it being the default, keep only the [model_providers.tokenaro] table, restore the old model_provider and model, and run `codex -c model_provider=tokenaro -c model=qwen3.8-27b` when you want it.)
Checked against the Codex configuration reference and advanced configuration on 2026-10-29. The removal of wire_api = "chat" is announced in openai/codex discussion #7782.
For Anthropic's Claude Code. It talks to Tokenaro's Anthropic-format endpoint (/v1/messages); the setup puts the gateway address, your key and the model names in ~/.claude/settings.json.
Anthropic does not support Claude Code with non-Claude models, so expect rough edges: it was tested here with file listing, reading and editing, but the WebSearch tool, Remote Control, voice dictation and fast mode are not available, and auto mode's safety checks run on a Tokenaro model instead of Claude.
Set it up yourself
-
Back up your user settings, if the file exists:
cp ~/.claude/settings.json ~/.claude/settings.json.bak-tokenaro -
Add these keys to the
envobject in~/.claude/settings.json(create the file if needed). If it already has settings or anenvobject, add the nine keys and keep everything else. Use only this user file, never a project's.claude/settings.json, which can end up in git.{ "env": { "ANTHROPIC_BASE_URL": "https://api.tokenaro.ai", "ANTHROPIC_AUTH_TOKEN": "YOUR_TOKENARO_KEY", "ANTHROPIC_MODEL": "qwen3.8-27b", "ANTHROPIC_DEFAULT_OPUS_MODEL": "qwen3.8-27b", "ANTHROPIC_DEFAULT_SONNET_MODEL": "qwen3.8-27b", "ANTHROPIC_DEFAULT_HAIKU_MODEL": "lfm3-audio-2b", "CLAUDE_CODE_SUBAGENT_MODEL": "qwen3.8-27b", "CLAUDE_CODE_MAX_CONTEXT_TOKENS": "200000", "CLAUDE_CODE_AUTO_MODE_SERVER": "0" } } -
Restart Claude Code and run
/status. “Anthropic base URL” should readhttps://api.tokenaro.ai, and an “Auth token” line should nameANTHROPIC_AUTH_TOKEN. A startup warning about two credentials means a claude.ai login is also saved; the key wins, and/logoutremoves the login. -
Test from a terminal (expect
OK):claude -p "Reply with just OK"
Undo: remove those nine keys from env (or cp ~/.claude/settings.json.bak-tokenaro ~/.claude/settings.json) and restart Claude Code. A saved claude.ai login is used again.
Or let your agent do it
Paste this into Claude Code. It tests the API first, makes the same changes, and reports back.
# Task: connect yourself (Claude Code) to Tokenaro
Tokenaro is a private AI server with an Anthropic-compatible Messages API. Point this Claude Code installation at it, test it, and report back. Work through the steps in order.
## Connection
- Base URL: https://api.tokenaro.ai (no /v1: Claude Code adds /v1/messages itself)
- Key: YOUR_TOKENARO_KEY (goes in ANTHROPIC_AUTH_TOKEN, sent as "Authorization: Bearer <key>")
- Models:
- qwen3.8-27b: the main model for all coding work. 262K context, tool use, reasoning.
- lfm3-audio-2b: small and fast, for background jobs (session titles, summaries). Not for coding.
## Rules
1. Back up first: cp ~/.claude/settings.json ~/.claude/settings.json.bak-tokenaro (if the file doesn't exist, say so; you will create it).
2. The key is a secret. Never print or echo it, and never put it in a project's .claude/settings.json or any file in a git repository. It goes only in ~/.claude/settings.json.
3. Change only the env keys below. Keep every other setting and every other env key.
## Step 1: Test the API before changing anything
curl -sS -w '\n%{http_code}\n' https://api.tokenaro.ai/v1/messages \
-H "Authorization: Bearer YOUR_TOKENARO_KEY" \
-H "anthropic-version: 2023-06-01" -H "content-type: application/json" \
-d '{"model":"qwen3.8-27b","max_tokens":400,"messages":[{"role":"user","content":"Reply with OK"}]}'
Pass = a JSON message containing OK, then 200. 401 = wrong or revoked key. 403 = the key may not use qwen3.8-27b. If it fails, stop and tell me.
## Step 2: Merge these keys into the "env" object of ~/.claude/settings.json
"ANTHROPIC_BASE_URL": "https://api.tokenaro.ai",
"ANTHROPIC_AUTH_TOKEN": "YOUR_TOKENARO_KEY",
"ANTHROPIC_MODEL": "qwen3.8-27b",
"ANTHROPIC_DEFAULT_OPUS_MODEL": "qwen3.8-27b",
"ANTHROPIC_DEFAULT_SONNET_MODEL": "qwen3.8-27b",
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "lfm3-audio-2b",
"CLAUDE_CODE_SUBAGENT_MODEL": "qwen3.8-27b",
"CLAUDE_CODE_MAX_CONTEXT_TOKENS": "200000",
"CLAUDE_CODE_AUTO_MODE_SERVER": "0"
(CLAUDE_CODE_MAX_CONTEXT_TOKENS tells Claude Code the context size to work within; without it, it warns at startup that the model "isn't recognized". CLAUDE_CODE_AUTO_MODE_SERVER=0 tells it that this server can't run auto mode's server-side safety checks, which stops a notice about classifier billing; Claude Code then runs those checks itself through Tokenaro, at no Anthropic charge.)
Then check the file is valid JSON: python3 -m json.tool ~/.claude/settings.json > /dev/null && echo valid
## Step 3: Test the new settings in a fresh process
Settings load when Claude Code starts, so test with a new headless run:
claude -p "Reply with just OK"
Pass = OK. Then tell me to restart Claude Code and run /status: "Anthropic base URL" should read https://api.tokenaro.ai, and an "Auth token" line should name ANTHROPIC_AUTH_TOKEN.
## Tell me these when you report
- Replies now come from qwen3.8-27b, not Claude: different quality, about 20-55 tokens/s.
- Not available through Tokenaro: the WebSearch tool (it needs Anthropic's server-side search), Remote Control, voice dictation and fast mode.
- A startup warning about two credentials means a claude.ai login is also saved. The key takes precedence; /logout removes the login.
- In auto mode, the check on whether an action is safe to run is made by a Tokenaro model, not Claude, so it is less reliable. Prefer normal permission prompts for risky work.
## How to undo
cp ~/.claude/settings.json.bak-tokenaro ~/.claude/settings.json (or delete the nine env keys), then restart Claude Code. A saved claude.ai login is used again.
Checked against Claude Code's gateway connection guide.
For any other app or agent that speaks the OpenAI API, or Anthropic's: chat apps, IDE assistants, automation tools or your own agents.
Set it up yourself
-
Open the app's model or provider settings and add an OpenAI-compatible provider. It may be called “OpenAI-compatible” or “custom OpenAI”, or be the OpenAI provider with a custom base URL.
-
Enter these values:
Base URL https://api.tokenaro.ai/v1 API key YOUR_TOKENARO_KEY Models qwen3.8-27b main: chat, coding, tools, images (262K) lfm3-audio-2b small and fast, not for coding (128K) deepseek-v4.1-flash slow, best answers; stream it (1M)If the app speaks Anthropic's format instead, use its Anthropic provider with the base URL
https://api.tokenaro.ai(no/v1), the same key and the same three model ids. -
Make
qwen3.8-27bthe default model, and uselfm3-audio-2bfor a small or fast model slot if the app has one. Allow at least 1,024 output tokens, and turn on streaming for long answers. Speech, OCR and voice models are listed under Models. -
Test: if the app has a “test connection” or “fetch models” button, use it, then send a short message. If that fails, check the key with the command in step 2 of Getting started.
Undo: remove the Tokenaro provider and switch the default model back.
Or let your agent do it
Paste this into the agent's chat. It finds its own settings, backs them up, makes the changes, tests each model, and reports back.
# Task: connect yourself to Tokenaro (an OpenAI-compatible AI server)
Tokenaro is a private AI inference server with an OpenAI-compatible API. Add it to your own configuration as an OpenAI-compatible provider and make its main model your default. Then test it and report back. Work through the steps in order.
## Connection
- Base URL: https://api.tokenaro.ai/v1 (all models and endpoints)
- API key: YOUR_TOKENARO_KEY
- Auth header: "Authorization: Bearer <key>" ("x-api-key: <key>" also works). HTTP 401 = wrong or revoked key. HTTP 403 = no key sent (blocked at the edge) or a model not on this key. HTTP 429 = this key's limits were hit (simultaneous requests or requests/minute): wait and retry.
- Prefer the Chat Completions API (POST /v1/chat/completions). The Responses API (POST /v1/responses) also works.
- If you can only speak Anthropic's Messages API: base URL https://api.tokenaro.ai (no /v1; the client adds /v1/messages), the same key and the same model ids.
## Models (exact ids)
- qwen3.8-27b: main model for chat, coding, tool calls and image input (image_url parts). Reasoning model. Context 262,144 tokens.
- lfm3-audio-2b: small, very fast helper for titles, short summaries, classification and quick voice replies. Accepts input_audio parts and has a real-time voice WebSocket. Context 128,000 tokens. Not for agentic coding.
- deepseek-v4.1-flash: very large and slow (runs on CPU). Highest quality on hard questions, when someone can wait. Reasoning model. Context 1,048,576 tokens. Always stream it.
- glm-ocr: document OCR. Send a page image (image_url part) plus the text "Text Recognition:" ("Table Recognition:" and "Formula Recognition:" also work); it returns Markdown.
- granite-speech-5.0-2b: speech-to-text, POST /v1/audio/transcriptions (multipart field "file"; response_format json or verbose_json).
- chatterbox-turbo: text-to-speech, POST /v1/audio/speech (input up to 4096 characters; response_format mp3 (default), opus, aac, flac, wav or pcm; one built-in English voice, "voice" is ignored).
- Real-time voice: wss://api.tokenaro.ai/v1/realtime?model=lfm3-audio-2b, OpenAI Realtime GA protocol, same Bearer header.
- Not available: embeddings, image generation, fine-tuning, files/batches/assistants.
## Facts that matter
- Reasoning models return their thinking in reasoning_content and the answer in content. Leave max_tokens headroom (1024+ even for a one-word answer, 4096+ for real work). Empty content with finish_reason "length" means the budget ran out.
- A request that sends nothing for 100 s is cut off (HTTP 524), so use "stream": true for long generations. Request bodies are capped at 100 MB.
## Rules
1. First find out where you keep model-provider settings (config file, settings store, environment variables) and how your version adds a custom OpenAI-compatible provider: an "OpenAI-compatible" or "custom OpenAI" provider, or the OpenAI provider with a custom base URL. Read your own documentation; don't guess key names. If it is unclear, tell me what you found and ask before changing anything.
2. Back up that configuration before editing it, and tell me where the backup is.
3. The key is a secret. Never print or echo it, and never put it in a log, a commit or our chat. Store it once, in your normal secret store (an environment variable TOKENARO_API_KEY or your secrets file, permissions 600), and reference it from the config. When you show me config, mask it (sk-...last 4). If the key under "Connection" still reads YOUR_TOKENARO_KEY, ask me to store it myself. Never send another provider's key to Tokenaro, and never send the Tokenaro key anywhere except api.tokenaro.ai.
4. Change only what this task needs. Don't remove other providers or touch unrelated settings.
## What to configure
- Provider: base URL https://api.tokenaro.ai/v1 plus the key.
- Default/main model: qwen3.8-27b (context 262,144). Write down the previous default first.
- Small/fast model slot, if you have one: lfm3-audio-2b (context 128,000).
- Optional extra model for hard, slow jobs: deepseek-v4.1-flash (context 1,048,576).
- Only if you support them already: speech-to-text granite-speech-5.0-2b, text-to-speech chatterbox-turbo, OCR glm-ocr, real-time voice as above.
## Test one small call per model you configured, and report the results
U=https://api.tokenaro.ai/v1; H="Authorization: Bearer $TOKENARO_API_KEY"
curl -sS "$U/models" -H "$H" # lists the models this key may use
curl -sS "$U/chat/completions" -H "$H" -H 'Content-Type: application/json' \
-d '{"model":"qwen3.8-27b","max_tokens":1024,"messages":[{"role":"user","content":"Reply with just OK"}]}'
(Repeat for each chat model you added. For deepseek-v4.1-flash add "stream":true and stop after the first data: lines. Test speech by writing POST $U/audio/speech output to a file, and transcription by sending that file to $U/audio/transcriptions.)
Then make one request through your own normal code path (not curl) on the new default model.
## Report back
- What you changed and where (key masked), the previous default model, and where the backup is.
- Every test: pass or fail, with a short excerpt of the output or the error.
- Anything you couldn't do or that needs me.
- Exactly how to undo it: restore the backup, or the specific settings to revert.
05 · Limits & tips
Limits and tips
- Your key has limits. Each key has a cap on simultaneous requests and on requests per minute. Going over either returns
HTTP 429. Wait a moment and retry, ideally with backoff. - Key errors. A request with no key at all is refused at the edge with
HTTP 403. A key that is wrong or has been revoked gets401, and a model that isn't on your key's list gets403. - Stream long answers. Cloudflare drops a request that sends nothing for 100 seconds and returns
HTTP 524. Use"stream": truefor anything long, and always fordeepseek-v4.1-flash. - Reasoning models need room.
qwen3.8-27banddeepseek-v4.1-flashthink before they answer. The thinking comes back inreasoning_content, separate fromcontent. Ifmax_tokensruns out while the model is still thinking,contentis empty andfinish_reasonis"length". Raise the limit: 1,024 or more even for short answers. - Always send
model. Requests are routed by the model id, and your key only works with the models on its list (GET /v1/models). - Size limits. A request body can be at most 100 MB, so downscale big images and split long recordings. Text-to-speech takes up to 4,096 characters per request.
- Privacy. The server does not store prompts or outputs. It keeps only usage counts per key.
- Keep your key private. Keep it on your machine or server, never in a web page or a public repository. If it leaks, ask for it to be revoked.
Capacity
How many people each model can serve at the same time, and the speed one person typically sees. These numbers are still being measured.
| Model | Simultaneous users | Typical speed per user |
|---|---|---|
qwen3.8-27b | — | — |
deepseek-v4.1-flash | — | — |
lfm3-audio-2b | — | — |
glm-ocr | — | — |
granite-speech-5.0-2b | — | — |
chatterbox-turbo | — | — |
Figures marked — haven't been measured yet.
06 · Help
Need a key, or something's wrong?
Ask the person who gave you your key. That applies to a new key, more models on your key, higher limits, and anything that's broken. Tokenaro has no sign-up: if you don't have a key yet, ask the person who invited you.
When something fails, tell them the time it happened, the model, and the HTTP status or error message. Never send the key itself; they can identify your key without it.