About 30 minutes · the first terminal in the course, and an n8n node for everyone who would rather not
Goal: make one real API call and recognise every part of the response.
Keys live at console.typesafe.ai/keys. The endpoint is a single POST (docs: API). Everything from the earlier lessons goes in the body unchanged:
curl -s https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"state": "Hi, I have been trying to connect my Stripe account for 3 days and it keeps failing. 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"}},
"is_urgent": {"type": "noul", "instructions": "The message conveys urgency or time-sensitivity"}
}
}'
model (the versioned id that answered), answers keyed by your ids, usage.input_tokens and usage.output_tokens. Per answer: type, then choice + probabilities + confidence, or score + legend + probabilities + confidence, or noul.TypeSafe states it does not train on customer data, offers a DPA, and provides zero data retention for enterprise plans (docs: Legal). Input is text only. Before sending a client's transcripts, read that page and strip what the questions do not need; less state is better state anyway (lesson 3).
Goal: the same request as typed objects, with retries handled.
Python (3.10+): pip install typesafe-sdk or uv add typesafe-sdk. The client reads TYPESAFE_API_KEY from the environment (docs: Python SDK).
from typesafe_sdk import TypeSafeClient, Choice, Score, Noul
client = TypeSafeClient() # AsyncTypeSafeClient for asyncio; RetryPolicy to tune backoff
r = client.system_one(
state=transcript,
questions={
"outcome": Choice("What the caller wants from this call",
{"sales_lead": "Wants a quote, purchase or consultation",
"support": "Existing customer with a technical or billing problem",
"vendor_or_spam": "Selling something to the company",
"unclear": "Too little information to tell"}),
"caller_mood": Score("How the caller comes across",
["Friendly or neutral", "Impatient or worried", "Angry or hostile"]),
"callback_requested": Noul("The caller asks to be called back or agrees to an appointment"),
},
)
outcome = r.answers["outcome"] # .choice, .probabilities, .confidence
mood = r.answers["caller_mood"].score
cb = r.answers["callback_requested"].noul
print(r.model, r.usage.input_tokens)
JavaScript / TypeScript (Node 20+): npm install @typesafe-ai/sdk (docs: JavaScript SDK).
import { TypeSafeClient, choice, score, noul } from "@typesafe-ai/sdk";
const client = new TypeSafeClient(); // TYPESAFE_API_KEY from env
const r = await client.systemOne({
state: transcript,
questions: {
outcome: choice("What the caller wants from this call", {
sales_lead: "Wants a quote, purchase or consultation",
support: "Existing customer with a technical or billing problem",
vendor_or_spam: "Selling something to the company",
unclear: "Too little information to tell",
}),
caller_mood: score("How the caller comes across", ["Friendly or neutral", "Impatient or worried", "Angry or hostile"]),
callback_requested: noul("The caller asks to be called back or agrees to an appointment"),
},
});
const { choice: outcome, confidence } = r.answers.outcome; // typed: outcome is one of the four keys
TYPESAFE_API_KEY (required), TYPESAFE_DEFAULT_MODEL (defaults to jev-latest; set it to a pinned version in production), TYPESAFE_BASE_URL, TYPESAFE_LOG_LEVEL (docs: Python usage).
Goal: wire Jev into an n8n or Make flow without writing a line.
There is no dedicated n8n node as of 2026-09-19; none is needed. The generic HTTP Request node does everything:
| Setting | Value |
|---|---|
| Method | POST |
| URL | https://api.typesafe.ai/v1/systemone |
| Authentication | Generic → Header Auth. Name Authorization, value Bearer <your key>. Store it as an n8n credential, not in the node. |
| Send body | JSON, "Using JSON". Paste the request from the Playground and replace the state with an expression. |
| Body (example) | {"state": {{ JSON.stringify($json.transcript) }}, "model": "jev-1.13.0", "questions": { … }} |
| Options | Retry on fail: on, 3 tries, 1000 ms wait (covers 429 and 529). |
Then the routing from lesson 4 as n8n nodes:
{{ $json.answers.outcome.choice }} equals unclear OR {{ $json.answers.outcome.confidence }} < 0.5answers.outcome.choice: CRM deal · support ticket · block · …answers.outcome.confidence and answers.outcome.choice.Goal: estimate a month's bill in your head and know when to pin a version.
| Fact (docs, 2026-09-19) | Value | What it means in practice |
|---|---|---|
| Price | $0.042 per million input tokens; output free | The recorded five-question call requests used 532 to 591 input tokens: about $0.000025 each. 100,000 calls a month ≈ $2.50. |
| Rate limits | 250,000 tokens/s and 1,200 requests/min (dynamic, may change) | 20 requests a second sustained. A backlog of 10,000 tickets clears in under ten minutes from one worker. |
| Context | 64k tokens per request; 32k for state plus the longest question | A long transcript fits. A whole day of transcripts does not, and should not (lesson 3). |
| Latency | About 100 ms model time in the docs; 72 to 167 ms recorded; 319 ms median end to end in the benchmark | Fast enough inside a voice-agent turn. |
| Languages | English primary; others supported with possibly lower accuracy | German worked on every recorded case here and 39 of 40 benchmark calls. Evaluate on your own language mix. |
| Training on your data | No; same weights for every account; no per-customer fine-tuning | Accuracy is improved by question design, not by uploading examples. |
jev-latest and jev-preview both point at jev-1.13.0 today. When TypeSafe ships 1.14, jev-latest will move, and thresholds tuned on 1.13 may no longer mean the same thing. The docs' advice: use the alias while exploring, pin the version ("model": "jev-1.13.0") once thresholds are set, and re-run the evaluation from level 5 before moving the pin (docs: Models). GET https://api.typesafe.ai/v1/models lists what is available. The response's model field always tells you which version actually answered; log it.
TypeSafe ships a skill that teaches the agent this whole course in compressed form, including "put questions and thresholds in one file" (docs: Agent skill):
claude plugin marketplace add typesafe-ai/skills
claude plugin install typesafe@typesafe-ai
# other agents:
npx skills add typesafe-ai/skills --skill typesafe-ai
The skill text itself is readable at github.com/typesafe-ai/skills. Every docs page is also available as Markdown by appending .md to its URL, which is the fastest way to give an agent exact, current facts.
Goal: run a two-week evaluation that produces thresholds instead of opinions.
The community benchmark's recommendation was not "switch to Jev". It was: run a focused shadow evaluation on your own traffic first (benchmark brief, recommendation). Here is that evaluation as a procedure:
unclear).choice, confidence, all probabilities, model, and what the human did.| Confidence band | Cases | Correct | Decision |
|---|---|---|---|
| ≥ 0.90 | 61 | 60 | act, including risky actions |
| 0.70 to 0.89 | 19 | 17 | act on cheap actions, confirm risky ones |
| 0.50 to 0.69 | 12 | 8 | confirm everything |
| < 0.50 | 8 | 3 | human |
If calibration holds, accuracy tracks the band. If it does not on your data, the questions need work before the thresholds do. The one 0.9+ miss in a table like this is your call 028: look at it, and add the option it wanted.
The community's benchmark exists because one member measured instead of guessing. Post your band table, the question set and the model version in the community thread. Failure modes that look systematic belong in the TypeSafe Discord, where the jaggedness page is maintained from exactly such reports.
Goal: leave with a wired decision and a plan to evaluate it.
1. A workflow tuned its thresholds on jev-1.13.0. What should the production request specify as model?
Aliases move. Thresholds were measured against one version's behaviour; pin it and re-evaluate before moving.
2. Roughly what does one five-question call transcript request cost at the documented price?
Recorded requests used 532 to 591 input tokens. At $0.042 per million that is about $0.000025, and output is free.
3. Which HTTP status should be retried with backoff?
429 and 529 are transient. 401 and 422 will fail the same way every time until you change something.
4. In the shadow evaluation, what sets the floor threshold?
Thresholds come from the accuracy-by-band table on your own data. The docs' 0.5 is a starting point for exploration, not a result.
Wire the decision you have carried through all six lessons into one HTTP node or one function, pinned to jev-1.13.0, with all three outlets. Log every answer. Label 40 real inputs this week. Come back in two weeks with a band table, and post it.
Everything in this course fits on one page: the cheat sheet. Terms are in the glossary. The primary sources, in reading order, are in the course index.