Quickstart: first call in HTTP, Python, JS
TL;DR Get a key at https://console.typesafe.ai/keys,
export TYPESAFE_API_KEY=..., thenPOST https://api.typesafe.ai/v1/systemonewithstate,model("jev-latest"), and aquestionsmap. Answers come back under the same keys you chose. Both SDKs readTYPESAFE_API_KEYfrom the environment and default tojev-latest.
Step 1 — get an API key
- Open the console at https://console.typesafe.ai and log in.
- Create a key.
The quickstart page points at the dashboard, https://console.typesafe.ai/keys — the same path the agent-skill page uses. Resolved 2026-09-21: until this refresh the quickstart linked https://console.typesafe.ai/settings/keys and this wiki flagged the two pages as disagreeing. They now agree on /keys. See console.typesafe.ai (console + playground).
Step 2 — set TYPESAFE_API_KEY
export TYPESAFE_API_KEY="sk-..." # your key from the console
Both SDK READMEs say the same thing: "Set TYPESAFE_API_KEY in your environment, then instantiate and use the client." For the HTTP API the key goes in the Authorization header instead. See TYPESAFE_* environment variables across SDKs for every TYPESAFE_* variable.
Step 3 (optional) — try it in the Playground first
- Open the Playground and log in.
- Paste any text as the state:
Hi, I've been trying to connect my Stripe account for 3 days and the integration keeps failing. I'm losing sales. Please help ASAP.
- Add a question. The docs suggest a Noul:
"Does this message express urgency?"
{
"urgency": {
"type": "noul",
"instructions": "Does this message express urgency?"
}
}
- Add more questions. "Mix Noul, Choice, and Score in one call and see all results at once."
Step 4 — the first call
The endpoint:
POST https://api.typesafe.ai/v1/systemone
Authorization: Bearer <API_KEY>
Content-Type: application/json
Three required body fields: state (string, object, or array), model (string), questions (map of id → question). See HTTP API: POST /v1/systemone and GET /v1/models for every field.
curl
Verbatim from the quickstart, complete and runnable once TYPESAFE_API_KEY is exported:
curl -X POST https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
"state": "Hi, I've been trying to connect my Stripe account for 3 days and the integration keeps failing. I'm losing sales. Please help ASAP.",
"model": "jev-latest",
"questions": {
"urgency": {
"type": "noul",
"instructions": "Does this message express urgency?"
}
}
}
EOF
The fuller request body the quickstart documents — one of each primitive in a single call:
{
"state": "Hi, I've been trying to connect my Stripe account for 3 days and the integration keeps failing. I'm losing sales. Please help ASAP.",
"model": "jev-latest",
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle this",
"criteria": {
"billing": "Payment or subscription issues",
"technical": "Bugs or integration problems",
"sales": "Pricing or account questions"
}
},
"frustration": {
"type": "score",
"instructions": "How frustrated the customer appears",
"criteria": [
"Calm, just stating facts",
"Frustrated but civil",
"Very angry, strong language"
]
},
"is_urgent": {
"type": "noul",
"instructions": "The message conveys urgency or time-sensitivity"
}
}
}
Python
Install (requires Python >= 3.10):
pip install typesafe-sdk
uv add typesafe-sdk
Use the SDK. The client reads
TYPESAFE_API_KEYfrom the environment and callsjev-latestby default.
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
client = TypeSafeClient()
ticket = "Hi, I've been trying to connect my Stripe account for 3 days and the integration keeps failing. I'm losing sales. Please help ASAP."
response = client.system_one(
state=ticket,
questions={
"department": Choice(
instructions="Which team should handle this",
criteria={
"billing": "Payment or subscription issues",
"technical": "Bugs or integration problems",
"sales": "Pricing or account questions",
},
),
"frustration": Score(
instructions="How frustrated the customer appears",
criteria=[
"Calm, just stating facts",
"Frustrated but civil",
"Very angry, strong language",
],
),
"is_urgent": Noul(
instructions="The message conveys urgency or time-sensitivity",
),
},
)
print(response.answers["department"].choice) # "technical"
print(response.answers["frustration"].score) # 1.0
print(response.answers["is_urgent"].noul) # 1.0
The repo README shows the same call as a context manager, which closes the underlying HTTP client:
from typesafe_sdk import Choice, TypeSafeClient
with TypeSafeClient() as client:
response = client.system_one(
state={"document": "I was charged twice. Please fix this ASAP."},
questions={
"category": Choice(
instructions="What is this ticket about?",
criteria={"billing": None, "technical": None, "other": None},
),
},
)
print(response.choices["category"].choice)
Two things to notice: criteria values may be null/None for options that need no description, and the README reads the answer through response.choices[...] while the docs read response.answers[...]. Both exist on SystemOneResponse — answers is the mixed map, and choices / nouls / scores are per-type views. See Python SDK responses, answers, usage, models. Score.criteria is an ordered sequence in the current Python SDK (0.7.1), not an int-keyed dict.
TypeScript / JavaScript
Install (Node.js 20 or newer):
npm install @typesafe-ai/sdk
Verbatim from the SDK README:
import { choice, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient();
const response = await client.systemOne({
state: { document: "I was charged twice. Please fix this ASAP." },
questions: {
category: choice("What is this ticket about?", {
billing: null,
technical: null,
other: null,
}),
},
});
console.log(response.answers.category.choice);
Answer types are inferred from your questions. The package includes ESM, CommonJS, and TypeScript declarations.
The same three-question call as the curl and Python samples, using the documented choice(instructions, criteria), score(instructions, criteria), and noul(instructions?, criteria?) helpers (adapted from the README, which shows only choice):
import { choice, noul, score, TypeSafeClient } from "@typesafe-ai/sdk";
const client = new TypeSafeClient();
const ticket =
"Hi, I've been trying to connect my Stripe account for 3 days and the integration keeps failing. I'm losing sales. Please help ASAP.";
const response = await client.systemOne({
state: ticket,
questions: {
department: choice("Which team should handle this", {
billing: "Payment or subscription issues",
technical: "Bugs or integration problems",
sales: "Pricing or account questions",
}),
frustration: score("How frustrated the customer appears", [
"Calm, just stating facts",
"Frustrated but civil",
"Very angry, strong language",
]),
is_urgent: noul("The message conveys urgency or time-sensitivity"),
},
});
console.log(response.answers.department.choice);
console.log(response.answers.frustration.score);
console.log(response.answers.is_urgent.noul);
See JavaScript/TypeScript SDK: install, client, choice/score/noul for client options and JavaScript SDK interfaces and type aliases for the inferred answer types.
Step 5 — read the answer
The response body for the three-question request, verbatim from the quickstart:
{
"model": "jev-1.13.0",
"answers": {
"department": {
"type": "choice",
"choice": "technical",
"confidence": 0.78,
"probabilities": {
"technical": 0.85,
"sales": 0.0,
"billing": 0.15
}
},
"frustration": {
"type": "score",
"score": 1.0,
"confidence": 1.0,
"legend": {
"0": "Calm, just stating facts",
"1": "Frustrated but civil",
"2": "Very angry, strong language"
},
"probabilities": {
"0": 0.0,
"1": 1.0,
"2": 0.0
}
},
"is_urgent": {
"type": "noul",
"noul": 1.0
}
},
"usage": {
"input_tokens": 392,
"output_tokens": 65
}
}
What each field means:
| Field | Applies to | Meaning |
|---|---|---|
model |
response | "The model that performed the evaluation." |
answers.<id> |
response | One answer per question, "keyed by the same ids you used in questions." |
type |
every answer | Matches the question's type. |
choice |
Choice | "The highest-probability option." |
score |
Score | "The probability-weighted answer across the levels; can land between levels." |
noul |
Noul | "The yes/no answer on a scale from 0 (no) to 1 (yes)." |
probabilities |
Choice, Score | Every option or level mapped to its probability ("floats that sum to 1"). |
legend |
Score | "Each level number mapped back to its description." |
confidence |
Choice, Score | "How certain the model is, derived from probabilities." Noul has no confidence. |
usage.input_tokens / usage.output_tokens |
response | Token usage for the request. |
Branch on these values in code — do not re-parse prose. See Confidence vs probability before you pick thresholds.
What changed on 2026-09-21. The quickstart was re-run and its sample state slightly reworded ("and the integration keeps failing"). Three things moved, and all three matter if you copied the old page:
- The Score answer now includes
probabilities. The old sample omitted it, and this page used to flag that as an inconsistency with raw/docs/api.md. All sources now agree the field is always present. modelin the response readsjev-1.13.0, the resolved version, not thejev-latestalias that was sent. That is the documented behaviour of the field (Models, aliases, pricing, rate limits, context); only the example changed.- The answers themselves changed:
departmentflipped frombilling(0.84, confidence 0.596) totechnical(0.85, confidence 0.78),frustrationfrom 1.035 to 1.0,is_urgentfrom 0.999 to 1.0. Do not treat any documented answer as an expected output — see Testing and evaluating a Jev workflow.
Step 6 — errors and retries
| Status | Meaning |
|---|---|
401 Unauthorized |
"Missing or invalid API key. Check the Authorization header." |
422 Unprocessable Entity |
"The request body failed validation — for example a missing required field or a malformed question." |
429 Too Many Requests |
"You have exceeded your rate limit. Back off and retry after a short delay." |
529 Overloaded |
"TypeSafe is temporarily overloaded. Retry after a short delay." |
When you receive a
429 Too Many Requestsor529 Overloadedresponse, retry the request with exponential backoff instead of retrying immediately. Our client SDKs handle this automatically, so no extra handling is needed if you use one of our SDKs with its default retry policy.
See HTTP status codes, rate limits, retry semantics.
Step 7 (optional) — install the agent skill
Install the TypeSafe skill using the Claude Code plugin or
npx skills add typesafe-ai/skills --skill typesafe-ai.
claude plugin marketplace add typesafe-ai/skills
claude plugin install typesafe@typesafe-ai
npx skills add typesafe-ai/skills --skill typesafe-ai
Then "tell your coding agent to use the TypeSafe skill as you build." The docs' starter prompt:
Let's build a simple CLI that uses the TypeSafe API to evaluate a set of supplied documents on multiple dimensions. Use the TypeSafe skill to understand how to use the TypeSafe API and how to structure the system. Ask me questions about what kinds of documents I want to evaluate and on what dimensions.
Full details in The typesafe-ai agent skill and Claude Code plugin.
Checklist
- Key created at console.typesafe.ai
-
TYPESAFE_API_KEYexported (or passed to the client explicitly) - One call returning
200with answers under your own question ids - Questions and thresholds kept in one file, not scattered through handlers
- Model pinned (
jev-1.13.0) if you have tuned thresholds —jev-latestmoves
Next steps
- Primitives: Choice, Score, Noul — "How to define questions, choose between Choice, Score, and Noul, and ask several at once"
- Choosing between Choice, Score, Noul — picking the right question type
- Confidence vs probability — "How TypeSafe reports certainty, and how to use it architecturally"
- Patterns overview — "Common patterns for building systems with TypeSafe"
- AI primer: why calibrated decision models — "Why TypeSafe trains models for calibrated decisions instead of generated text"
- HTTP API: POST /v1/systemone and GET /v1/models — every request and response field
- Python SDK: install, clients, system_one() / JavaScript/TypeScript SDK: install, client, choice/score/noul — installation options and detailed usage
- Models, aliases, pricing, rate limits, context — aliases, price, rate limits, context window
- Cookbooks overview — worked end-to-end examples
- Playbook for LLM agents building with Jev — if you are an agent building with Jev
Related
- System One Models — what Jev is and why the output is typed
- State: what you send Jev — what to put in
state - console.typesafe.ai (console + playground) — the console and playground
- TYPESAFE_* environment variables across SDKs —
TYPESAFE_*variables - Smart home assistant demo walkthrough — a worked application
Sources
- raw/docs/introduction__quickstart.md (https://docs.typesafe.ai/introduction/quickstart)
- raw/docs/introduction.md (https://docs.typesafe.ai/introduction)
- raw/github/typesafe-sdk-python/README.md (https://github.com/typesafe-ai/typesafe-sdk-python)
- raw/github/typesafe-sdk-js/README.md (https://github.com/typesafe-ai/typesafe-sdk-js)
- raw/docs/api.md (https://docs.typesafe.ai/api)