Jev is TypeSafe AI’s decision model. You send it a piece of state and a set of typed questions, and it answers with probabilities instead of prose. This guide covers the Jev API key and your first request; for the background on what Jev is and why it returns numbers instead of text, read what is Jev first. To be clear, since the search results are messy: this is Jev the TypeSafe AI model, not FaZe Jev the YouTuber and not the JEV vaccine.
A Jev API key works like any other bearer token, so if you’re new to the pattern, what is an API key covers the basics. Direct API access is in early access, so step one is getting off the waitlist. After that you’ll create the key, learn the request shape, call the endpoint with curl and the Python SDK, read the probability fields, and wire the request into Apidog with assertions on those probabilities. If you can’t wait, the same model is on Vercel AI Gateway with no waitlist listed; the FAQ covers that route.
Step 1: get early access, then create the key
Jev is early access as of this writing. TypeSafe’s launch post says it is “bringing developers off the waitlist as quickly as we can,” so join the waitlist on typesafe.ai and wait for the console invite; there’s no self-serve signup yet. Once your console account is active, go to console.typesafe.ai/settings/keys and create a key. Copy it once and treat it like a password.
Export it as an environment variable instead of pasting it into code:
export TYPESAFE_API_KEY="ts_..."
The official curl examples and the Python SDK both read TYPESAFE_API_KEY from the environment, so one variable covers every example below. If a key ever lands in a commit, rotate it in the console and run an API key leak check across the repo.
Step 2: understand the request shape
Every Jev call is a single POST https://api.typesafe.ai/v1/systemone with three body fields, documented in the TypeSafe API reference:
| Field | Type | What it is |
|---|---|---|
model |
string | jev-latest (resolves to jev-1.13.0 today) or jev-preview for the newest build |
state |
string, object, or array | The content to evaluate: a ticket, a JSON record, a message history |
questions |
map of name to question | The typed questions Jev answers against the state |
Each question is one of three primitives:
| Primitive | Request criteria | Response fields |
|---|---|---|
noul (yes/no) |
optional {"true": "...", "false": "..."} |
noul: 0 (no) to 1 (yes) |
choice |
required map of option to description, up to 255 options | choice, confidence, probabilities per option |
score |
required ordered array of 2 to 10 level descriptions | score, confidence, legend, probabilities per level |
The response also carries model and usage.input_tokens / usage.output_tokens. Questions of different types can share one state and come back in one round trip.
Step 3: make the first request with curl
This request runs all three primitives against one support ticket:
curl https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "jev-latest",
"state": "My card was charged twice for one order and nobody has replied in three days.",
"questions": {
"needs_review": {
"type": "noul",
"instructions": "Does this ticket need a human agent?",
"criteria": {
"true": "money, legal, or an unanswered complaint",
"false": "a routine question a bot can close"
}
},
"route": {
"type": "choice",
"instructions": "Route this ticket to a team.",
"criteria": {
"billing": "payment or charge problems",
"shipping": "delivery problems",
"technical": "application bugs"
}
},
"urgency": {
"type": "score",
"instructions": "How urgent is this ticket?",
"criteria": ["low", "medium", "high"]
}
}
}'
A response looks like this (values are illustrative):
{
"model": "jev-1.13.0",
"answers": {
"needs_review": { "type": "noul", "noul": 0.97 },
"route": {
"type": "choice",
"choice": "billing",
"confidence": 0.98,
"probabilities": { "billing": 0.98, "shipping": 0.01, "technical": 0.01 }
},
"urgency": {
"type": "score",
"score": 1.6,
"confidence": 0.62,
"legend": { "0": "low", "1": "medium", "2": "high" },
"probabilities": { "0": 0.02, "1": 0.36, "2": 0.62 }
}
},
"usage": { "input_tokens": 190, "output_tokens": 0 }
}
Step 4: read the probability fields
Read the numbers precisely:
noulis the probability of “yes”. 0.97 means Jev is 97% sure this ticket needs a human.choiceis the highest-probability option,probabilitieslists every option, andconfidencetells you how decisive the pick was. A 0.98 route is safe to automate; a 0.51 route with 0.47 on the runner-up is a coin flip.scoreis the probability-weighted position across the ordered levels, so 1.6 sits between “medium” (1) and “high” (2).legendmaps each index back to its label, andprobabilitiesshows the full spread.
Because the output is a distribution, not a label, you set the threshold, not the model. That’s why the assertions in Step 6 test numbers.
Step 5: the same call with the Python SDK
Install the SDK; the client picks up TYPESAFE_API_KEY from the environment:
pip install typesafe-sdk
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
with TypeSafeClient() as client:
response = client.system_one(
state="My card was charged twice for one order and nobody has replied in three days.",
questions={
"needs_review": Noul(
instructions="Does this ticket need a human agent?",
criteria={"true": "money, legal, or an unanswered complaint",
"false": "a routine question a bot can close"},
),
"route": Choice(
instructions="Route this ticket to a team.",
criteria={"billing": "payment or charge problems",
"shipping": "delivery problems",
"technical": "application bugs"},
),
"urgency": Score(
instructions="How urgent is this ticket?",
criteria=["low", "medium", "high"],
),
},
)
print(response.nouls["needs_review"].noul)
print(response.choices["route"].choice, response.choices["route"].probabilities)
print(response.scores["urgency"].score)
Answers are grouped by type on the response object (nouls, choices, scores). There’s a JavaScript SDK with the same shape, and if you’re already on Vercel AI Gateway, experimental_evaluate from AI SDK 7 calls the model as typesafe-ai/jev, with one difference: the boolean question type returns a probability field instead of noul.
Step 6: store and test the Jev API key in Apidog
Curl proves the key works once. Apidog makes the request rerunnable, assertable, and mockable for the whole team.
Store the key as a secret variable. Create an environment called TypeSafe and add TYPESAFE_API_KEY as a secret so its value stays masked in the UI and out of exports; Apidog environments and secret variables walks through the setup. Set the auth on the request to Bearer Token with {{TYPESAFE_API_KEY}} as the value.
Build the POST request. Add a POST to https://api.typesafe.ai/v1/systemone, paste the JSON body from Step 3, and send it. The response panel renders the answers tree, so you can check the probabilities before writing an assertion.
Assert on probabilities, not on prose. In the visual assertion builder, point JSONPath expressions at the fields you care about:
$.answers.needs_review.noulis greater than0.9$.answers.route.choiceequalsbilling$.answers.route.probabilities.billingis greater than0.8$.answers.urgency.scoreis greater than or equal to1$.usage.input_tokensis less than1000
If you prefer scripts, the post-processor accepts the familiar pm API:
const body = pm.response.json();
pm.test("ticket flagged for a human", () => {
pm.expect(body.answers.needs_review.noul).to.be.above(0.9);
});
pm.test("routed to billing", () => {
pm.expect(body.answers.route.choice).to.eql("billing");
});
Save it as a test scenario. Drop the request into a test scenario with a small CSV of tickets and expected routes, and run it on every change to your instructions or criteria. Prompt edits are code changes; a ten-row scenario catches the edit that quietly moves a 0.95 to a 0.6. The same scenario runs in CI through the Apidog CLI, so a regression blocks the merge.
Mock the declared response shape. Define the response schema on the endpoint (the three answer objects plus usage), and Apidog’s smart mock serves realistic fake probabilities immediately. The frontend can build the “needs review” badge and routing UI against the mock before the backend ships, then swap the mock URL for the real endpoint with one environment change.
For planning seats: Apidog’s Free plan includes 4 users, and paid tiers are per seat.
Thresholds in code
Once the assertions pass, the same numbers drive production logic. Keep thresholds in one place and name them:
REVIEW_THRESHOLD = 0.9
AUTO_ROUTE_CONFIDENCE = 0.85
needs_review = response.nouls["needs_review"].noul >= REVIEW_THRESHOLD
route = response.choices["route"]
if route.confidence >= AUTO_ROUTE_CONFIDENCE and not needs_review:
assign(ticket, team=route.choice)
else:
queue_for_human(ticket, suggested=route.choice)
Log the full probabilities map with every decision so you can tune thresholds from real data later, and make human review the default when confidence is low; the model is telling you it isn’t sure.
Limits, pricing, and models
Straight from the TypeSafe models page:
| Item | Value |
|---|---|
| Price | $0.042 per million input tokens; output tokens aren’t charged |
| Rate limits | 250,000 tokens per second and 1,200 requests per minute, adjusted dynamically under load |
| Context | 64k tokens per request; 32k for the state plus the longest single question |
| Input | Text only: a string, JSON object, or array. No images, audio, or video |
| Language | English gives the best accuracy; other languages work but not equally well |
| Aliases | jev-latest is the stable default; jev-preview tracks the newest release |
At that price a million short tickets run under $10. TypeSafe also states that Jev is not trained on customer requests or responses.
Common errors and how to fix them
| Status | Meaning | Fix |
|---|---|---|
| 401 Unauthorized | Missing or invalid API key | Check the Authorization: Bearer header and that the env variable is set in the shell or environment you’re running from |
| 422 Unprocessable Entity | Request body failed validation | Common causes: a choice without criteria, a score with fewer than 2 levels, a misspelled type, or questions sent as an array instead of a map |
| 429 Too Many Requests | Rate limit exceeded | Back off with jitter and retry; batch several questions into one request to cut request count |
| 529 Overloaded | TypeSafe is temporarily overloaded | Retry with exponential backoff; the request is safe to repeat |
A 422 is the one you’ll hit most while iterating; the endpoint schema from Step 6 catches most of them before the request leaves your machine.
FAQ
Is there a free tier for the Jev API? The public docs list per-token pricing and don’t describe a free tier or starter credits, and access itself is waitlisted for now. Check the console once your invite lands for the current offer, and treat any figure you see elsewhere as unofficial.
Can I get several answers from one request? Yes. questions is a map, so a noul, a choice, and a score can all run against one state in a single call. It’s cheaper than three requests and keeps the answers consistent because they share one input.
How is this different from structured outputs on a chat model? Structured outputs force a language model to emit valid JSON, but the values inside are still generated tokens, and a “confidence” field is text the model wrote about itself. Jev returns measured probabilities as the native output, which is why you can assert noul > 0.9 and trust the comparison.
Do I need TypeSafe’s SDK if I’m on Vercel? No. The Vercel AI SDK exposes Jev through experimental_evaluate with typesafe-ai/jev as the model id. You’ll authenticate with your AI Gateway key instead of a TypeSafe key, and the boolean answer comes back as probability.
Next steps
You now have a Jev API key, a working request in curl and Python, and a clear read on noul, choice, and score. Put the request into Apidog, add the probability assertions, and save the test scenario so prompt edits are tested like code. Download Apidog to follow along.



