# Jev

URL: https://docs.hanzo.ai/docs/guides/migrate/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`](https://docs.hanzo.ai/docs/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

```bash
# 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`](https://github.com/hanzoai/benchmarks/blob/c61361f/decision/harness/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](https://docs.hanzo.ai/docs/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](https://docs.hanzo.ai/docs/decisions#response)) |
| 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](https://docs.hanzo.ai/docs/decisions#errors) are listed on
Decisions.

## The call

Jev, on OpenRouter:

```bash
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.

```bash
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:

```json
{
  "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`, `state` and `questions`, and the optional
  `provider`, `session_id`, `user` and `trace`. Kai accepts the optional four
  and uses none of them.
- **The questions.** `choice`, `noul` and `score`, each with `type`,
  `instructions` and `criteria`: a choice maps each label to its description, a
  noul may describe `true` and `false`, a score lists its levels lowest first.
- **The answers.** `choice`, `noul`, `score`, `confidence`, `probabilities` and
  `legend`, in the same shapes, under the names you gave. `confidence` is
  `(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_tokens`_\* and \*_`usage.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](https://docs.hanzo.ai/docs/models/kai#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](https://openrouter.ai/typesafe/jev-1.13)). 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](https://docs.hanzo.ai/docs/decisions#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.
