"$0.017 per million tokens" is a price per token. Your bill is per request, and the two can rank models differently. This post pulls live prices for six open-weight models and prices one realistic workload in realtime and in batch. Then it sends the same request to each model to show where per-token prices mislead.
All numbers were pulled on 2026-11-11 from https://synap402.com/v1/models, the public catalog of Synap (the OpenAI-compatible API we build). Every script below ran today, and the outputs are pasted as they came out, with one cosmetic exception that's noted.
1. The price list, straight from the API
The catalog is public JSON, so you don't need a key to read it. The self-hosted models are the ones with a batch price. Aliases and routers don't have one.
# prices.py: live per-1M-token prices for the self-hosted models, plus a workload estimate import json, urllib.request with urllib.request.urlopen("https://synap402.com/v1/models") as r: models = json.load(r)["data"] # Self-hosted models are the ones with a batch price; aliases and routers have none. own = [m for m in models if m.get("pricing", {}).get("basis") == "engine" and "batch_prompt_per_million" in m["pricing"]] REQS, IN_TOK, OUT_TOK = 10_000, 2_000, 500 # the workload to price print(f"{'model':36} {'in/1M':>8} {'out/1M':>8} {'ctx':>8} {'realtime':>10} {'batch':>9}") for m in sorted(own, key=lambda m: m["pricing"]["prompt_per_million"]): p = m["pricing"] rt = REQS * (IN_TOK * p["prompt_per_million"] + OUT_TOK * p["completion_per_million"]) / 1e6 bt = REQS * (IN_TOK * p["batch_prompt_per_million"] + OUT_TOK * p["batch_completion_per_million"]) / 1e6 print(f"{m['id']:36} {p['prompt_per_million']:>8} {p['completion_per_million']:>8} " f"{m['context_length']:>8} {rt:>10.2f} {bt:>9.2f}") $ python3 prices.py model in/1M out/1M ctx realtime batch gpt-oss-20b 0.017 0.085 65536 0.77 0.38 gemma-4-26b-a4b-it 0.04 0.21 32768 1.85 0.93 qwen3-coder-30b 0.0675 0.27 98304 2.70 1.35 qwen/qwen3-30b-a3b-instruct-2507 0.081267 0.2438 98304 2.84 1.42 qwen/qwen3-vl-30b-a3b-instruct 0.14 0.55 65536 5.55 2.77 meta/muse-glimmer-30b 0.28 1.1 131072 11.10 5.55 Cleaned up, in USD per 1M tokens:
| model | input | output | batch in / out | context | 10k × (2k in + 500 out), realtime | same, batch |
|---|---|---|---|---|---|---|
| gpt-oss-20b | 0.017 | 0.085 | 0.0085 / 0.0425 | 65,536 | $0.77 | $0.38 |
| gemma-4-26b-a4b-it | 0.04 | 0.21 | 0.02 / 0.105 | 32,768 | $1.85 | $0.93 |
| qwen3-coder-30b | 0.0675 | 0.27 | 0.03375 / 0.135 | 98,304 | $2.70 | $1.35 |
| qwen3-30b-a3b-instruct-2507 | 0.0813 | 0.2438 | 0.0406 / 0.1219 | 98,304 | $2.84 | $1.42 |
| qwen3-vl-30b-a3b-instruct | 0.14 | 0.55 | 0.07 / 0.275 | 65,536 | $5.55 | $2.77 |
| muse-glimmer-30b | 0.28 | 1.10 | 0.14 / 0.55 | 131,072 | $11.10 | $5.55 |
The worked example is 10,000 requests with 2,000 input and 500 output tokens each, so 20M tokens in and 5M out. For gpt-oss-20b that is 20 × $0.017 + 5 × $0.085 = $0.34 + $0.425 = $0.765. Batch halves every line.
Two things stand out:
- Output tokens weigh more than you'd think. Output costs 3–5× input on every model here. In this workload the 500 output tokens are a fifth of the tokens but about half of the bill.
- Context is a hard limit, not a price. Gemma is cheap, but 32,768 tokens rules it out for long-document work no matter what it costs.
2. Same request, five models: where per-token prices lie
The table assumes every model writes 500 tokens. They don't. Here is one real support ticket classified by five of them, with each result priced from the live catalog:
# measure.py: send the same real request to each model and price what came back import json, os, urllib.request from openai import OpenAI client = OpenAI(base_url="https://synap402.com/v1", api_key=os.environ["SYNAP_API_KEY"]) with urllib.request.urlopen("https://synap402.com/v1/models") as r: catalog = {m["id"]: m for m in json.load(r)["data"]} TICKET = ("Hi, I was charged twice for my October invoice (#4471) and the second " "charge still hasn't been refunded. Can someone look at this today?") PROMPT = ("Classify this support ticket. Answer with JSON only: " '{"category": "billing|bug|how-to|other", "urgent": true|false}\n\n' + TICKET) for mid in ["gpt-oss-20b", "gemma-4-26b-a4b-it", "qwen3-coder-30b", "qwen/qwen3-30b-a3b-instruct-2507", "meta/muse-glimmer-30b"]: r = client.chat.completions.create(model=mid, max_tokens=400, messages=[{"role": "user", "content": PROMPT}]) p, u = catalog[mid]["pricing"], r.usage cost = (u.prompt_tokens * p["prompt_per_million"] + u.completion_tokens * p["completion_per_million"]) / 1e6 answer = (r.choices[0].message.content or "").strip().replace("\n", " ") print(f"{mid:34} {u.prompt_tokens:>4} in {u.completion_tokens:>4} out ${cost:.7f} {answer[:60]}") gpt-oss-20b 128 in 178 out $0.0000173 {"category":"billing","urgent":true} gemma-4-26b-a4b-it 80 in 17 out $0.0000068 ``` json {"category": "billing", "urgent": true} qwen3-coder-30b 72 in 12 out $0.0000081 {"category": "billing", "urgent": true}
qwen/qwen3-30b-a3b-instruct-2507 72 in 12 out $0.0000088 {"category": "billing", "urgent": true}
meta/muse-glimmer-30b 117 in 199 out $0.0002517 {"category": "billing", "urgent": true}
All five got it right. Scaled to 10,000 tickets: | model | per request | × 10,000 | |---|---|---| | gemma-4-26b-a4b-it | $0.0000068 | $0.07 | | qwen3-coder-30b | $0.0000081 | $0.08 | | qwen3-30b-a3b-instruct-2507 | $0.0000088 | $0.09 | | gpt-oss-20b | $0.0000173 | $0.17 | | muse-glimmer-30b | $0.0002517 | $2.52 | **The cheapest model per token came fourth per request.** gpt-oss-20b is a reasoning model. It produced 178 output tokens, most of them thinking, to write a 36-character answer. Its chat template also turned the same prompt into 128 input tokens where Qwen counted 72. The two Qwen models answered in 12 tokens. For short, structured answers, a non-reasoning model at 3–4× the token price came out at under half the cost. The opposite holds when the answer is long and the reasoning is what you pay for. Then gpt-oss-20b's $0.085 output price is hard to beat. Measure your own prompt. Don't rank models by the price column. Also note Gemma wrapped its JSON in a Markdown fence. If you parse the output, use `response_format` (Gemma, gpt-oss and qwen3-coder accept `json_schema` here) and don't strip fences by hand. ## 3. Batch: the same job at half price If nobody is waiting on the answer (nightly classification, evals, back-filling embeddings or summaries), send the requests as a batch. It's the standard OpenAI Batch API, so the official SDK works unchanged: ```python # batch.py: the same job at half price through the OpenAI Batch API import json, os, time from openai import OpenAI client = OpenAI(base_url="https://synap402.com/v1", api_key=os.environ["SYNAP_API_KEY"]) tickets = [ "I was charged twice for invoice #4471, please refund the second charge.", "How do I rotate my API key without downtime?", "The export button returns a 500 error since this morning.", ] with open("requests.jsonl", "w") as f: for i, t in enumerate(tickets): f.write(json.dumps({ "custom_id": f"t{i}", "method": "POST", "url": "/v1/chat/completions", "body": {"model": "qwen3-coder-30b", "max_tokens": 50, "messages": [{"role": "user", "content": 'Classify as JSON {"category": "billing|bug|how-to|other"}: ' + t}]}, }) + "\n") f = client.files.create(file=open("requests.jsonl", "rb"), purpose="batch") b = client.batches.create(input_file_id=f.id, endpoint="/v1/chat/completions", completion_window="24h") while b.status not in ("completed", "failed", "expired", "cancelled"): time.sleep(10) b = client.batches.retrieve(b.id) print(b.status, b.request_counts) for line in client.files.content(b.output_file_id).text.splitlines(): r = json.loads(line) body = r["response"]["body"] print(r["custom_id"], body["usage"]["prompt_tokens"], body["usage"]["completion_tokens"], body["choices"][0]["message"]["content"].strip()) Output (the JSON fences in the answers are collapsed here for space):
completed BatchRequestCounts(completed=3, failed=0, total=3) t0 44 14 {"category": "billing"} t1 36 15 {"category": "how-to"} t2 40 14 {"category": "bug"} This small batch came back in about 15 seconds, but the contract is a 24-hour window, and live traffic goes first. Each line is billed when it completes, and a line that fails isn't billed. Batch is only available on the self-hosted models in the table. Other catalog models are refused when you create the batch.
Takeaways
- Price the request, not the token. Run your real prompt once per candidate model and multiply. It takes ten lines (above).
- Reasoning models are a per-request tax on short answers. Use them where the thinking is what you need.
- Output tokens weigh more. Cap
max_tokens, ask for JSON, and use structured outputs. - Anything that can wait a day costs half.
- Read prices from the API, not from a blog post, this one included.
/v1/modelsis the source, and the scripts above re-run in a second.
The same catalog also has larger models (GLM 5.3, DeepSeek V4, Qwen3.5 122B) for when a 30B model isn't enough. Their prices are on the pricing page.
Links: live price list at synap402.com/cheapest-llm-api · API docs · try models in the browser without signing up at synap402.com/chat. Keys start at $10, and top-ups don't expire. You can also pay per top-up in USDC on Base via x402, with no account.
Synap by Linkrra: one OpenAI-compatible API for open models. synap402.com


