Jev Python SDK: Install, Examples and Error Handling
Last checked · Independent guide, not affiliated with TypeSafe AI
Install the official SDK with pip install typesafe-sdk (Python 3.10 or newer), set TYPESAFE_API_KEY, and call client.system_one(state=..., questions={...}) with Noul, Choice and Score objects. The SDK retries rate limits and server errors automatically and raises typed exceptions such as TypeSafeAuthenticationError.
The official Python client is typesafe-sdk (imported as typesafe_sdk). It talks to TypeSafe’s own API only; for OpenRouter or Vercel, use those platforms’ SDKs. Everything below was run with version 0.7.0 on Python 3.10 on September 19, 2026.
Install
Section titled “Install”pip install typesafe-sdk # or: uv add typesafe-sdkIt requires Python 3.10 or newer.
Mirror gotcha. If pip says No matching distribution found for typesafe-sdk==0.7.0 and lists only older versions, your pip is probably using a package mirror that has not synced yet. That happened to us with a mirror that only had 0.6.0. Install from PyPI directly:
pip install -i https://pypi.org/simple typesafe-sdkVersion 0.7.0 switched the SDK’s serialization library to pydantic; if you pinned 0.6.x, read the changelog before upgrading.
Authenticate
Section titled “Authenticate”The client reads TYPESAFE_API_KEY from the environment. You can also pass api_key= to the constructor. With no key at all, the SDK raises TypeSafeError: No API key was provided... before sending anything.
Ask questions
Section titled “Ask questions”from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
client = TypeSafeClient(model="jev-1.13.0")
message = "I was charged twice for my annual plan this morning. Please refund one of the charges today."
response = client.system_one( state=message, questions={ "wants_refund": Noul(instructions="Is the customer asking for money back?"), "queue": Choice( instructions="Which team should handle this message?", criteria={ "billing": "Charges, invoices, refunds", "technical": "Bugs, outages, integrations", "sales": "New plans, upgrades, quotes", }, ), "urgency": Score( instructions="How urgent is this message?", criteria=["Can wait a week", "Should be handled this week", "Needs a reply today"], ), },)
print(response.answers["wants_refund"].noul) # 0.99print(response.answers["queue"].choice) # billingprint(response.answers["urgency"].score) # 2.0print(response.usage) # input_tokens=418 output_tokens=72import asynciofrom typesafe_sdk import AsyncTypeSafeClient, Choice, Noul
async def main(): async with AsyncTypeSafeClient(model="jev-1.13.0") as client: result = await client.system_one( state="My invoice lists two seats, but only one of us can sign in.", questions={ "is_bug": Noul(instructions="Does the customer describe something not working?"), "queue": Choice( instructions="Which team should handle this message?", criteria={"billing": None, "technical": None, "other": None}, ), }, ) print(result.nouls["is_bug"].noul, result.choices["queue"].choice)
asyncio.run(main())The sync example took 557 ms end to end in our run. Besides response.answers[...], the response has typed shortcuts: response.nouls, response.choices and response.scores.
Typed responses with response_model
Section titled “Typed responses with response_model”Since 0.7.0 you can pass a pydantic model to get attribute access and type checking:
from typesafe_sdk import ChoiceAnswer, Noul, Choice, NoulAnswer, SystemOneResponse, TypeSafeClient
class Triage(SystemOneResponse): is_bug: NoulAnswer queue: ChoiceAnswer
with TypeSafeClient(model="jev-1.13.0") as client: r = client.system_one( "My invoice lists two seats, but only one of us can sign in.", { "is_bug": Noul(instructions="Does the customer describe something not working?"), "queue": Choice(instructions="Which team should handle this message?", criteria={"billing": None, "technical": None, "other": None}), }, response_model=Triage, ) print(r.is_bug.noul, r.queue.choice, r.request_id)Choosing a model
Section titled “Choosing a model”Pass model= to the client; the default is jev-latest. Use the full versioned ID jev-1.13.0. The shortened jev-1.13, which appears in one example in TypeSafe’s docs, fails with TypeSafeBadRequestError: ... 400 Unknown model: jev-1.13. To see what your account can use:
print([m.name for m in client.models.list().models]) # ['jev-latest', 'jev-preview']Errors, retries and timeouts
Section titled “Errors, retries and timeouts”| Exception | When |
|---|---|
TypeSafeError |
No API key configured (raised locally) |
TypeSafeAuthenticationError |
401: invalid key |
TypeSafeBadRequestError |
400: unknown model, unknown question type, max_tokens_exceeded |
TypeSafeUnprocessableEntityError |
422: missing required field |
TypeSafeRateLimitError |
429 after retries are exhausted |
TypeSafeInternalServerError |
5xx (including 529) after retries |
TypeSafeAPITimeoutError, TypeSafeAPIConnectionError |
Network problems after retries |
Messages include the method, URL, status and a request_id, for example POST https://api.typesafe.ai/v1/systemone: 401 Cannot authenticate with the server... (request_id=req_...).
By default the client retries 408, 429 and all 5xx responses, plus timeouts and connection errors, twice after the first attempt, with backoff starting at 0.5 seconds and capped at 5 seconds, and it honors Retry-After. To change this:
from typesafe_sdk import RetryPolicy, TypeSafeClient
client = TypeSafeClient( model="jev-1.13.0", retry=RetryPolicy(max_retries=4, backoff_max=10.0),)- Create one client and reuse it; use it as a context manager (
with/async with) or close it when done. - Put all the questions for one input into one
system_onecall. It costs about the same and takes the same time as a single question. - Keep question text and thresholds in one module so they are easy to review and re-test.
See also: Your first Jev call, Jev API reference and Jev API errors.