Jev
Jev answers typed questions through OpenRouter's Decisions API. Here that is POST /v1/decisions with model kai: the same request body at a new base URL with a Hanzo key, billed $0.021 per million input tokens against Jev's $0.042.
Jev is TypeSafe's decision model. Through OpenRouter it is
POST https://openrouter.ai/api/alpha/decisions with
"model": "typesafe/jev-1.13". Hanzo serves the same wire at
POST https://api.hanzo.ai/v1/decisions, where kai is
Hanzo's decision model. The question types, their criteria and the answers
are the Decisions API's on both sides, so a Jev request becomes a Kai request
when three things change: the base URL, the key and model.
Start here
# 1. mint a key — a decision takes a secret key; a pk- is refused with 403
curl -sS -X POST https://api.hanzo.ai/v1/account/keys \
-H "Authorization: Bearer $HANZO_SESSION" \
-H 'Content-Type: application/json' \
-d '{"type":"secret"}'
# 2. a Jev request with its base URL, key and model changed
curl -sS https://api.hanzo.ai/v1/decisions \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H 'Content-Type: application/json' \
-d '{"model": "kai",
"state": "The second charge on my March invoice is still pending.",
"questions": {"is_bug": {"type": "noul", "instructions": "Is this a product defect?"}}}'
# 3. kai's row in the model catalogue, with its price; no key needed
curl -sS https://api.hanzo.ai/v1/models | jq '.data[] | select(.id == "kai")'Step 2 needs an org whose plan covers paid models; without one it answers
402 with "code": "plan_required" and a link to pick a plan. Step 3 answers
"outputs": ["decision"] and "pricing" with "input_per_million": 0.021 and
"output_per_million": 0: the dollars per million input tokens every kai call
is billed at ("prompt": "0.000000021" is the same rate per token).
What changes
The Jev column is OpenRouter's wire as the benchmark harness called it for
11,099 questions
(three_way.py);
the Kai column is what api.hanzo.ai answers.
| Jev on OpenRouter | Kai on Hanzo | |
|---|---|---|
| Endpoint | POST https://openrouter.ai/api/alpha/decisions | POST https://api.hanzo.ai/v1/decisions |
| Key | an OpenRouter key | a Hanzo secret key, sk-; see API keys |
model in the request | typesafe/jev-1.13 | kai |
model in the answer | the dated snapshot that served, typesafe/jev-1.13-20260917 in the harness's run | kai; the checkpoint is routing.checkpoint, kai-1.2 |
id | OpenRouter's | dec_ and 32 hex digits |
provider | the provider OpenRouter routed to | Hanzo |
usage.output_tokens | as OpenRouter counts them, not billed | 0: Kai generates nothing |
usage.cost | the call's price in dollars | not sent: the price is usage.input_tokens at $0.021 per million, and each call is a row in GET /v1/billing/usage |
| Fields only Hanzo sends | — | answer_confidence on each choice and score answer; routing, state_hash and latency_ms on the response (Decisions) |
| Price | $0.042 per million input tokens | $0.021 per million input tokens |
Hanzo checks a request before any work: 1 to 100 questions, a body of at most
16 MiB, and at most 256 characters of session_id or user. Kai reads each
question with the state beside it, up to 32,768 tokens together, and each
option whole up to 512 tokens; a request past either is refused with 422
before any work. A key that sends faster than its per-minute rate is answered
429 with Retry-After. The errors are listed on
Decisions.
The call
Jev, on OpenRouter:
curl https://openrouter.ai/api/alpha/decisions \
-H "Authorization: Bearer $OPENROUTER_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "typesafe/jev-1.13",
"state": {"message": "I was charged twice for my March invoice and the second charge is still pending."},
"questions": {
"is_bug": {"type": "noul", "instructions": "Is this a product defect?",
"criteria": {"true": "a defect", "false": "working as intended"}},
"team": {"type": "choice", "instructions": "Which team should handle it?",
"criteria": {"account": "logins and profiles",
"payments": "charges, invoices and refunds",
"product": "features and bugs"}},
"urgency": {"type": "score", "instructions": "How urgent is it?",
"criteria": ["later", "this week", "today", "now"]}
}
}'Kai, on Hanzo: the same body, with model changed.
curl https://api.hanzo.ai/v1/decisions \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kai",
"state": {"message": "I was charged twice for my March invoice and the second charge is still pending."},
"questions": {
"is_bug": {"type": "noul", "instructions": "Is this a product defect?",
"criteria": {"true": "a defect", "false": "working as intended"}},
"team": {"type": "choice", "instructions": "Which team should handle it?",
"criteria": {"account": "logins and profiles",
"payments": "charges, invoices and refunds",
"product": "features and bugs"}},
"urgency": {"type": "score", "instructions": "How urgent is it?",
"criteria": ["later", "this week", "today", "now"]}
}
}'kai-1's answer, recorded on its v0.3.14 runtime on CPU, with latency_ms left
out because it is a measurement. kai-1.2, which api.hanzo.ai serves now,
differs in three places: routing.checkpoint is kai-1.2, routing.reach is
32768, and a noul answer is noul alone, read by a new yes/no reader, so
is_bug will not match:
{
"id": "dec_b9c80aaf7c6d56d83d1cee9fa3aa013e",
"model": "kai",
"provider": "Hanzo",
"answers": {
"is_bug": {"type": "noul", "noul": 0.40156308478254465, "confidence": 0.1968738304349107,
"answer_confidence": 0.5984369152174553},
"team": {"type": "choice", "choice": "payments", "confidence": 0.9998402014322358,
"probabilities": {"account": 3.643255552012215e-05, "payments": 0.9998934676214906,
"product": 7.00998229893226e-05},
"answer_confidence": 0.9998934676214906},
"urgency": {"type": "score", "score": 1.784693056354545, "confidence": 0.08352544779484232,
"legend": {"0": "later", "1": "this week", "2": "today", "3": "now"},
"probabilities": {"0": 0.11681518789817974, "1": 0.2761086470523921,
"2": 0.31264408584613174, "3": 0.2944320792032965},
"answer_confidence": 0.31264408584613174}
},
"usage": {"input_tokens": 63, "output_tokens": 0},
"routing": {
"backend": "kai",
"checkpoint": "kai-1",
"sha256": "0834a74f2d140642a453373da09e5a128e3d2c8d9e8bb1dcaf86d7210a4ecdfc",
"calibration": "cal_e23c27a1f768bff7",
"device": "cpu",
"reach": 1024,
"trained": 1024,
"positions": 8192,
"extrapolated": false,
"reason": "explicit model='kai'"
},
"state_hash": "sha256:3893b9038056e7104d3c76ffa6f703dea7ce1d0b4bb8be153e4adbc3432b212b"
}team is settled. urgency is not: no level holds more than 0.32, and its
confidence is 0.0835. Kai's probabilities are not rounded. Billed, the call is
its 63 input tokens: 63 × $0.021 / 1,000,000 = $0.000001323.
What stays the same
- The request.
model,stateandquestions, and the optionalprovider,session_id,userandtrace. Kai accepts the optional four and uses none of them. - The questions.
choice,noulandscore, each withtype,instructionsandcriteria: a choice maps each label to its description, a noul may describetrueandfalse, a score lists its levels lowest first. - The answers.
choice,noul,score,confidence,probabilitiesandlegend, in the same shapes, under the names you gave.confidenceis(n·p_max − 1)/(n − 1)on both, so a threshold carries over as arithmetic. What a threshold buys is the model's own calibration, so tune it again on your outcomes. usage.input_tokensandusage.output_tokens.- Errors past the gateway:
{"error": {"code", "message"}}on both.
What you gain
Half the price per input token. Kai is billed $0.021 per million input tokens and Jev $0.042, with output free on both. Each model counts tokens with its own tokenizer. Kai counts the request's own text once: the state, and each question's instructions, labels, descriptions and levels.
Higher accuracy on 11 of 12 suites, measured on kai-1. On the frozen
harness, kai-1 is more accurate than Jev on every suite but jailbreak, 0.9175
against 0.940, and on 30 of the 51 MASSIVE languages. Those runs were measured
on CUDA in BF16; kai-1.2, which production serves on CPU, answers yes/no with a
new reader and has no harness run posted. Suite by suite:
Kai's results. Its probabilities are less well
calibrated than Jev's on four suites (expected calibration error, Kai against
Jev): typed decisions 0.171 against 0.047, toxicity 0.195 against 0.177,
jailbreak 0.074 against 0.047 and Banking77 0.074 against 0.073. A threshold
tuned on Jev's confidence is to be tuned again.
Speed
One yes/no question takes 38 ms of server time at p50 in production (kai-1.2,
on CPU); 100 in one request take 13.9 s. Each response's latency_ms is the
decision's wall time in the model; time your own calls end to end. Each Kai
replica answers one request at a time, so a long request, many questions or
many options, delays the requests behind it on that replica.
What does not carry
A long state. Kai was trained on 1,024 tokens of state beside each
question. It reads up to 32,768 and marks a longer read
routing.extrapolated: true; those answers are unreliable: a fact at the end of
a 3,982-token state read 0.507. Past 32,768 it refuses with 422 and code
state_too_long, naming the tokens it needs. An option is read whole up to 512
tokens; a longer one is option_too_long. Jev's window is 32,000 tokens
(OpenRouter). Keep the state to what
decides the question, or split a long document across calls.
A yes/no question's criteria. kai-1.2 accepts them and does not read
them: on JevBench's 74 described yes/no items its P(true) is the same with the
sides as written, negated or swapped.
usage.cost. Jev's answer states its price; Kai's states
usage.input_tokens, billed at $0.021 per million. The org's spend is
GET /v1/billing/usage, one row per call, model kai.
Self-hosting. Kai runs at api.hanzo.ai only: its weights and its runtime
are not published, so it cannot be self-hosted.
Jev through Hanzo. typesafe/jev-1.13 sent to api.hanzo.ai is forwarded
to OpenRouter and billed $0.042 per million input tokens
(pricing). It is partly unavailable: a Jev request
can be answered 503 with code upstream_unavailable and the message
typesafe/jev-1.13 is temporarily unavailable, with a Retry-After. Retry it,
or call Jev on OpenRouter directly.