Jev SDK: Your First Working Call
What people search for as the Jev SDK ships under the TypeSafe name: there is no package called jev-sdk, and looking for one is the first half-hour most people lose. This page is the other route — install the right package, make one call that returns a real answer, read the object that comes back, and recognise the three errors that stop people before the first success. Jev itself is a decision model rather than a chat model (what Jev AI is), and the HTTP details behind these libraries are on the Jev API page.
Install the Jev SDK
It ships as two official packages, both open source under the MIT licence, both reading their key from the TYPESAFE_API_KEY environment variable and defaulting to the jev-latest model against https://api.typesafe.ai. Versions below were current on 2026-09-21.
Python
pip install typesafe-sdkThe Python Jev SDK is version 0.7.0 and needs Python 3.10 or newer — the type syntax it uses will not parse on 3.9. The import name is typesafe_sdk, with an underscore, not a hyphen.
JavaScript / TypeScript
npm install @typesafe-ai/sdkThe JavaScript Jev SDK is version 0.6.0 and wants Node 20 or newer. It ships ESM, CommonJS and TypeScript declarations in one package, and the answer types are inferred from the questions you pass, so your editor knows which labels a choice can return before you run anything.
Your first Jev SDK call, end to end
One support ticket, three questions of different types, answered in a single request. Both versions do the same thing:
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
# The key is read from TYPESAFE_API_KEY. Never hard-code it.
with TypeSafeClient() as client:
result = client.system_one(
state="I paid yesterday by card and still have no activation code.",
questions={
"category": Choice(
instructions="Which queue should handle this message",
criteria={
"not_received": "Paid but has not received the product",
"refund": "Asking for a refund",
"presale": "Has not bought yet, asking about price",
},
),
"urgency": Score(
instructions="How time-sensitive is this",
criteria=["Just asking", "Wants it today", "Needs it immediately"],
),
"is_frustrated": Noul(instructions="The customer sounds frustrated"),
},
)
print(result.choices["category"].choice)
print(result.scores["urgency"].score)
print(result.nouls["is_frustrated"].noul)import { choice, noul, score, TypeSafeClient } from "@typesafe-ai/sdk";
// The key is read from TYPESAFE_API_KEY. Never hard-code it.
const client = new TypeSafeClient();
const { answers } = await client.systemOne({
state: "I paid yesterday by card and still have no activation code.",
questions: {
category: choice("Which queue should handle this message", {
not_received: "Paid but has not received the product",
refund: "Asking for a refund",
presale: "Has not bought yet, asking about price",
}),
urgency: score("How time-sensitive is this", [
"Just asking",
"Wants it today",
"Needs it immediately",
]),
is_frustrated: noul("The customer sounds frustrated"),
},
});
console.log(answers.category.choice, answers.urgency.score);Three things are worth noticing. The client takes no arguments in the common case — key, base URL and model all come from the environment, which is why nothing secret appears in the code. The question names (category, urgency, is_frustrated) are yours, and they become the keys your answers arrive under, so pick names your own code wants to read. And all three questions travel in one request: Jev bills input tokens only, so asking three things about one ticket costs one ticket, not three.
If you would rather see a response before installing anything, the playground runs the same payload in the browser and exports it as Python, TypeScript or curl.
Reading the response
The Jev SDK hands back a result carrying the model name, token usage, and an answer object per question. Every answer is typed, so there is no JSON to parse and no schema to validate — but each of the three types hands back something different, and the extra fields are the useful part.
choice — the pick and the probabilities
You get choice, one of the labels you defined; confidence; and probabilities, a number for every label. Routing on the label alone throws away the interesting half. When the top two labels sit at 0.41 and 0.39, that ticket wants a human, and the probabilities are the only place that shows.
score — the number and the legend
The score is indexed from zero in the order you listed your criteria, and it is an expected value, so it can land between levels: 1.6 on a three-step scale means the input sits most of the way toward the top step. You also get a legend mapping each level back to the wording you supplied, which is what lets you log a readable label without keeping your rubric in two places.
noul — a single probability
A yes/no question comes back as one number from 0 to 1 under noul. Not a boolean: you set the threshold, and you can set a different one for auto-closing a ticket than for flagging it. In Python the convenience maps result.choices, result.scores and result.nouls group answers by type; in JavaScript everything sits on answers with the type inferred.
Three errors you will probably hit
Three that cost us real time on the way to a working Jev SDK call, in the order they tend to happen.
1. Reaching for a chat client instead
Skip the Jev SDK, point an OpenAI-compatible library at the model, and the server rejects the call with is a decisions model and cannot be used with the chat/completions endpoint. Nothing about your key or your payload is wrong; the route is. The endpoint explained covers why. Using the official package avoids this entirely, because it only knows how to call the correct path.
2. The .env file that never loaded
Write TYPESAFE_API_KEY = "your-key-here" — spaces around the equals sign, quotes around the value — and then run set -a; . ./.env, and the shell never sets the variable at all: with spaces around the equals sign that line is not an assignment, it is a command. The SDK then raises a missing-key error while your file plainly contains the key, which is the worst kind of bug to read. Confusingly, some frameworks load the same file correctly — Next.js parses it fine — so the same file works in one terminal and not the other. Write TYPESAFE_API_KEY=your-key-here with no spaces, and check with echo $TYPESAFE_API_KEY before blaming the library.
3. A request body the server refuses
Send too much state, or too many questions in one call, and the server rejects it. This one masquerades as flakiness because people assume a large payload timed out, but it is a refusal, not a network failure: the SDK raises an API error with a status rather than a connection or timeout error, and retrying unchanged will fail identically every time. Read the status before adding retries. Splitting a long document into sections, or a long question list into two calls, is the fix.
One rule underneath all three: the key belongs on the server. Never bundle it into a browser build, never commit it, and keep every Jev SDK example on environment variables so a copied snippet can never carry a live credential.
Frequently asked questions
Is there an official Jev SDK?
Yes, published by TypeSafe under its own name rather than the model’s: typesafe-sdk on PyPI and @typesafe-ai/sdk on npm, both MIT licensed. A package named for Jev itself does not exist, which is why the search leads nowhere.
Which Python version does it need?
Python 3.10 or newer. On 3.9 the install may resolve but the import fails on syntax.
Can I call Jev from the browser?
You should not. Anything running in a page ships its key to every visitor, and the JavaScript Jev SDK refuses browser environments by default for exactly that reason — the option that overrides it is named dangerouslyAllowBrowser, which tells you how the authors feel about it. Put the call behind your own endpoint.
How do I handle errors?
Both Jev SDK packages raise typed errors rather than returning status codes: separate classes for authentication, bad request, rate limiting, server errors, timeouts and connection failures. Catch the rate-limit and connection cases, which are worth retrying, and let authentication and bad-request errors fail loudly — retrying those just wastes time. Retries with backoff are already built in and on by default.
Does the Jev SDK support async?
In Python, yes — there is an async client alongside the synchronous one with the same method surface. The JavaScript client is promise-based throughout, so it is async by construction.