On this page
- What do you need before you start?
- Step 1 — install the SDK and set the key
- Step 2 — make your first decision
- Step 3 — gate on confidence instead of trusting every answer
- Step 4 — call the raw API when you need to
- Step 5 — make the integration production-safe
- Where AI coding assistants get this wrong
- Checklist
- FAQ
- Related topics
- Sources
How to Set Up Jev in Your App (API Key, SDK, and First Decision)
A step-by-step Jev setup: create a key, install the SDK, send typed questions, read confidence, and add a fallback before it touches users.
Quick answer
- Create a TypeSafe API key, store it in TYPESAFE_API_KEY, and install typesafe-sdk for Python or @typesafe-ai/sdk for Node.
- Send one state and a dictionary of questions; each question is a Choice, Score, or Noul.
- Read the typed answer and its confidence, then decide in code whether to act, fall back to an LLM, or send the request to a human.
- Pin a model version for production instead of jev-latest, and log the version that answered.
- Verify with one unambiguous input and one deliberately ambiguous input before wiring the call into a user path.
What do you need before you start?
You need a TypeSafe account and API key, Python 3.10 or newer if you use the Python SDK, or Node.js 20 or newer for the JavaScript SDK. You also need one decision the app actually makes, written as a closed question. “Which team should handle this ticket?” is ready. “What should we do about this customer?” is not, because the options and the action are still undefined.
Keep the key server-side. The SDK reads TYPESAFE_API_KEY from the environment, and that environment should be the server process, not a browser bundle or a mobile app. If the call has to happen in a client, put your own endpoint in front of it and keep the TypeSafe key on the server. A key in a frontend build is a public key, regardless of how it is named.
Step 1 — install the SDK and set the key
Create a virtual environment so the SDK and its dependencies stay out of the system Python, then install the package. The Python distribution is named typesafe-sdk and imports as typesafe_sdk. For Node, install @typesafe-ai/sdk and mark the project as an ES module if it is not already.
Set the key in your shell for the first run. In a real project, put it in an untracked .env file or your platform’s secret store, and add the local file to .gitignore. The example below exports it directly so the first call has no extra moving parts.
python3 -m venv .venv
.venv/bin/python -m pip install typesafe-sdk
export TYPESAFE_API_KEY="your-api-key"
# Node equivalent
# npm install @typesafe-ai/sdk
# npm pkg set type=module
# export TYPESAFE_API_KEY="your-api-key"Step 2 — make your first decision
The first call should answer a question you can check by eye. The example triages a support ticket into a department, scores frustration, and asks whether the message is urgent. All three questions go in one request, and Jev evaluates them against the same state. Save the file and run it with the same virtual environment’s Python.
The Python SDK exposes typed collections for each answer type: response.choices, response.scores, and response.nouls. Use the question IDs you supplied. If you are following along with Node, the answers are under response.answers and the question helpers are choice, score, and noul. The values in the output are examples and can change between model versions.
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
ticket = (
"Our API integration started returning 500 errors 20 minutes ago, "
"and we cannot process any customer orders until this is fixed."
)
with TypeSafeClient(model="jev-latest") as client:
response = client.system_one(
state=ticket,
questions={
"department": Choice(
instructions="Which team should handle this?",
criteria={
"billing": "Payments, invoices, and refunds",
"technical": "Bugs, outages, and integrations",
"sales": "Pricing, plans, and upgrades",
},
),
"frustration": Score(
instructions="How frustrated does the customer appear?",
criteria=[
"Calm, stating facts",
"Frustrated but civil",
"Very angry, strong language",
],
),
"is_urgent": Noul(
instructions="Does the message convey urgency or time-sensitivity?",
),
},
)
print(response.choices["department"].choice)
print(response.choices["department"].confidence)
print(response.scores["frustration"].score)
print(response.nouls["is_urgent"].noul)
# Example output:
# technical
# 0.93
# 1.02
# 0.99Step 3 — gate on confidence instead of trusting every answer
A confident answer and a correct answer are different things, and the gap is where production incidents live. Put the threshold in code, not in a prompt. For a routing decision, route to the model’s choice only when confidence clears the bar; send the rest to a human queue or to a stronger model. For a yes/no question, threshold the probability at a level chosen from the cost of a false positive versus a false negative.
The function below returns a small decision object rather than acting directly. That shape makes the policy testable: you can call it with a fixture and assert that a low-confidence answer goes to review. It also keeps the side effect out of the model call, which is what lets you retry or shadow the decision without sending a refund or closing a ticket twice.
from dataclasses import dataclass
from typesafe_sdk import Choice, Noul, TypeSafeClient
MODEL = "jev-1.14.0"
ROUTE_THRESHOLD = 0.75
@dataclass(frozen=True)
class TicketDecision:
route: str
urgent: bool
confidence: float
reason: str
def classify_ticket(ticket: str) -> TicketDecision:
with TypeSafeClient(model=MODEL) as client:
response = client.system_one(
state=ticket,
questions={
"department": Choice(
instructions="Which queue should handle this ticket?",
criteria={
"billing": "Invoices, payments, and refunds",
"technical": "Bugs, outages, and integrations",
"account": "Login, passwords, and profile access",
"human_review": "Ambiguous, sensitive, or none of the above",
},
),
"urgent": Noul(
instructions="Does the ticket describe an outage or lost revenue?",
),
},
)
department = response.choices["department"]
urgent = response.nouls["urgent"].noul >= 0.9
if department.confidence < ROUTE_THRESHOLD:
return TicketDecision(
route="human_review",
urgent=urgent,
confidence=department.confidence,
reason="below_confidence_threshold",
)
return TicketDecision(
route=department.choice,
urgent=urgent,
confidence=department.confidence,
reason="model_choice",
)Step 4 — call the raw API when you need to
The SDK is the shortest path, but the HTTP endpoint is useful when you are debugging, integrating from a language without an SDK, or comparing providers behind one adapter. The request body is the same state-plus-questions shape. Send the key in the Authorization header and read the answers object. A 4xx is your request; a 5xx or timeout is a reason to fall back, not to retry forever.
The raw response also exposes the probability distribution and the usage counts, which the typed SDK surface may hide. Log the distribution for low-confidence cases: it is the difference between a model that is unsure between two plausible queues and one that is unsure across five. That distinction changes whether a human review is enough or whether the question itself needs rewriting.
curl --fail-with-body --silent --show-error \
https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
--data-binary @- <<'JSON'
{
"model": "jev-1.14.0",
"state": "I was charged twice for March. Please refund the duplicate.",
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {
"billing": "invoices, payments, refunds",
"technical": "bugs, outages, integrations",
"account": "login, passwords, profile access"
}
},
"is_urgent": {
"type": "noul",
"instructions": "Is the customer asking for immediate action?"
}
}
}
JSONStep 5 — make the integration production-safe
Pin the model version. jev-latest is convenient while exploring, but a model change can move your thresholds and your error rates. Log the returned model version with every decision, set a request timeout, and define a fallback for timeouts and rate limits: a human queue is usually the honest one for a routing decision, and a stronger model is reasonable for a non-critical yes/no check.
Treat the input as potentially sensitive. A support ticket can contain names, order IDs, and occasionally credentials. Send only the fields the questions need, redact what you can, and check the vendor’s data-retention terms for your plan. Add a test with a known ambiguous input and a known clear input, and assert both the answer and the threshold behavior. That test is what catches a broken integration before a user does.
Where this bites vibecoders
An assistant will happily wire the API key into a React component, put the prompt in a string, and call the endpoint from the browser. That leaks the key and turns every client into a potential spammer. The other common shortcut is to skip the fallback because the demo answers look good. Spend the extra twenty minutes on a server-side call, a confidence threshold, and a human-review path; those three choices are what separate a demo from something you can leave running.
Where AI coding assistants get this wrong
- Putting the TypeSafe key in frontend code or a committed .env file.
- Using jev-latest in production and being unable to reproduce a past decision.
- Acting on every answer regardless of confidence.
- Sending the entire customer record when two fields would answer the question.
- Retrying a 4xx request forever instead of fixing the malformed question.
- Testing only with the examples from the documentation.
Checklist
- Create a server-side key and store it outside the repository.
- Install the SDK in a virtual environment or a pinned Node project.
- Write one atomic question per decision you need.
- Run the first call and check the answer by eye.
- Add a confidence threshold and a fallback path.
- Pin the model version and log it with each decision.
- Add tests for a clear input and an ambiguous input.
FAQ
Where do I get a TypeSafe API key?
From the TypeSafe console at console.typesafe.ai. Create a key for the project, store it in a server-side environment variable, and do not commit it. Access terms and free credit have changed since launch, so check the console for the current plan and limits rather than relying on a launch post.
Which SDK package do I install?
For Python, install typesafe-sdk; the import name is typesafe_sdk. For JavaScript and TypeScript, install @typesafe-ai/sdk. Both read TYPESAFE_API_KEY from the environment by default. The SDKs expose typed answer collections, which is why the examples read response.choices rather than parsing raw JSON.
Why is my answer below the confidence threshold?
Usually because the question is ambiguous or the options overlap. Two labels with similar descriptions, a state that contains several unrelated requests, or a rubric whose levels are not clearly ordered all produce flatter distributions. Rewrite the criteria first, then re-test; lowering the threshold hides the problem rather than fixing it.
Should I use jev-latest or a pinned version?
Use jev-latest while exploring and a pinned version such as jev-1.14.0 in production. A pinned version keeps the behavior you evaluated, and logging the returned version lets you explain a past decision. When you upgrade, re-run your labeled set and compare accuracy, calibration, and low-confidence rate before switching.
Can I call Jev from the browser?
Not safely with your own key. A key shipped to a browser is visible to anyone who opens developer tools, and the endpoint can be called with it outside your app. Put a small server endpoint in front of Jev, authenticate your own users, and keep the TypeSafe key on the server.
Related topics
- What Is Jev? TypeSafe’s Decision-Only Model Explained
- What Is a Decision Model? Jev, Laya, and Typed Answers Instead of Parsed Prose
- Jev vs Laya vs Clef: Which Decision Model Should You Use?
- How to Add a Decision Model to a Vibecoded App
- How to Evaluate a Decision Model Before You Ship It
- How to Sandbox an AI Coding Agent