Skip to content

Jev Repos & SDKs

The Python SDK in production: sync, async, retries, and exceptions

Sync, async, retries, exceptions, and typed questions: what the Python SDK gives you, what it leaves to your code, and how to pin it.
Published
Category
Jev Repos & SDKs
Resources cited
12
Reading time
6 min
Tags#sdk#python#production#integrations

The Python SDK is small enough to read in an afternoon, but most production incidents come from the parts that are easy to skip: which client you instantiate, how retries spend their budget, and which exception tells you to fix the request rather than send it again. The TypeSafe Python SDK gives you a sync and an async client over the same request shape, with separate usage and API documentation.

Two clients, one request shape

Install with pip install typesafe-sdk or uv add typesafe-sdk; the quickstart says Python 3.10 or newer. TypeSafeClient is synchronous and AsyncTypeSafeClient is asynchronous, but both take the same state and questions and return the same response models. Both also work as context managers, so a with or async with block closes the underlying connections. The key comes from TYPESAFE_API_KEY, which you create in the TypeSafe console before the first call.

The split is a class boundary, not a configuration flag. Choose the client at the edge of your application and keep request-building shared. Every request goes to POST /v1/systemone, the endpoint documented in the raw API reference, with model, state, and questions at the top level. The SDK adds retries, typed parsing, and the key, but it does not change the wire contract.

Questions and state are type-checked

Questions are objects, not dictionaries with stringly typed fields. Noul(instructions=...), Choice(instructions=..., criteria={...}), and Score(instructions=..., criteria=[...]) are the three constructors. The questions page is explicit about the rules:

  • state cannot be None. Values inside an object may be None, and the state itself can be text, a JSON object, or an array of text.
  • A Noul needs at least one of instructions or criteria; with neither, the SDK raises TypeSafeError before sending.
  • Choice.criteria is a required map from label to a description or None. The label is the option text.
  • Score.criteria is a non-empty ordered sequence, one description per level starting at zero.
  • Question dictionaries with a type key can be mixed with the typed objects in the same request.

The Pydantic models set additionalProperties: false, so a misspelled field is rejected rather than ignored. That is the right failure mode, but an upgrade can reject payloads that previously passed, so keep a contract test for every question shape your service sends.

A common mistake is stuffing unrelated context into one string. The state guide recommends named object fields, and the SDK enforces the container, not the content design.

Answers come back grouped by type

The response model is SystemOneResponse, which is frozen, strict, and ignores unknown fields. Its required fields are model and usage; usage reports input_tokens and output_tokens, and request_id reads the x-typesafe-request-id header. The responses page lists the access patterns:

  • Noul: response.nouls["key"].noul, a single probability from 0 to 1.
  • Choice: response.choices["key"].choice, plus a confidence and a probability per option.
  • Score: response.scores["key"].score, an expected value that can fall between integer levels, plus a confidence, legend, and per-level probabilities.

Answers are keyed by the question ids you sent, and those ids are never sent to the model. Adding a question does not change the answers to the others, which is the basis for batching many judgments into one call, as covered in One Request, Many Questions.

What the retry policy actually does

The retry page documents a RetryPolicy dataclass. Its defaults, not the example in the usage guide, are:

SettingDefaultEffect
max_retries2Retries after the first attempt; 0 disables retries
backoff_initial0.5sDoubles each attempt up to backoff_max
backoff_max5sCeiling for a single delay
backoff_jitter0.25Subtracts a random fraction of each delay
timeout30sTotal budget for the call, including delays

Retries fire on 408, 429, and 500-599, plus connection and timeout errors. respect_retry_after honors Retry-After and retry-after-ms when the server sends them. You can add exception types through exceptions, or supply a predicate that forces another attempt.

Two details matter in production. First, timeout is a total budget, not a per-attempt timeout; the client stops before a retry whose delay would reach or exceed it and re-raises the last error. Second, these are the dataclass defaults, and the retry page flags that the usage guide shows a different example. Copy the values you intend to use.

Retries are still bounded work. A 30-second budget inside a 10-second request deadline is a bug: the caller gives up while the SDK is still backing off. Set the SDK budget below your request budget, and treat the retry policy and exponential backoff as part of latency planning. Retries and Failure Modes covers the wider failure taxonomy.

Exceptions are the control flow

The exception hierarchy is documented on the exceptions page. Everything descends from TypeSafeError. HTTP failures become TypeSafeAPIError, which carries status, body, headers, a credential-free endpoint, and request_id; subclasses cover 400, 401, 403, 404, 422, 429, and 5xx. TypeSafeRateLimitError exposes retry_after_ms. TypeSafeAPIConnectionError is a request that got no HTTP response, and TypeSafeAPITimeoutError subclasses it.

The one that surprises teams is TypeSafeAPIResponseValidationError: the call returned 2xx, but the body failed the response model. Its dotted field_path such as answers.tone.confidence is enough to distinguish a server contract change from a bad request. Catch it separately and alert; retrying will not fix it.

A simple production policy: 401, 403, and 422 mean fix the credential or payload; 429 and 5xx go to the SDK within budget, then fail the unit of work; connection and timeout errors are retried only when the operation is safe to repeat; response validation errors page the owner.

That last case connects to idempotency: a timed-out decision that only read a state can be repeated, but a decision that already triggered a side effect cannot. The API has no idempotency key, so put the deduplication in the code that applies the answer.

Async changes the failure surface

AsyncTypeSafeClient is an async context manager with await client.system_one(...). It accepts response_model to parse the body into a Pydantic model; without it you get SystemOneResponse. It also accepts extra_body, shallow-merged last, so a colliding state, model, or questions key overrides the typed field. That is useful for a gateway-specific field and dangerous if a user-controlled map reaches it.

The async client page documents the rest. Per-call retry and timeout overrides apply to that request only, which is how you give a batch job a longer budget without loosening the interactive path. client.models.list() returns names, descriptions, and release dates.

One caveat is easy to miss: request and response bodies are not redacted in typesafe_sdk logs, even though secret headers are. If your state contains customer text, configure logging before the first request.

Pin versions and test the upgrade

The changelog shows a young package moving quickly. v0.5.7 on 14 September 2026 was the initial release. v0.6.0 made Score.criteria an ordered sequence, and v0.7.0 moved serialization from msgspec to Pydantic and added response_model; both are breaking. v0.7.1 validates the key early, v0.7.2 adds http2, and v0.7.3 on 9 October 2026 fixes async TLS crashes with truststore<=0.10.4.

Pin the SDK, pin the model, and keep a contract test for every question shape. A versioned model id such as jev-1.13.0 is safer than an alias when thresholds were tuned against it. Test upgrades in staging against your production states, not the quickstart.

The SDK is thin: the request shape is portable, the response types are explicit, and the failure modes are named. The work is in the configuration around it. Set a retry budget below your request deadline, catch the validation error separately, keep state out of logs, and pin both the package and the model. Skipping that turns a fast decision into an intermittent incident.

From the Jev directory

The resources behind this article.

The raw HTTP contract behind every SDK: POST a state plus typed noul, choice, and score questions to /v1/systemone, and get one answer per question, with error codes and retry guidance.
Jev Guides & ArticlesDocs#official#docs#api
Official
Get an API key, manage models, and try the playground: three walkthrough lessons plus realistic use cases like resume screening and ticket routing.
Jev Tools & CLIsPlayground#official#playground#api-keys
Official
298GitHub stars
Official sync and async Python client for the TypeSafe API with typed questions, retry policies, and inferred answers. `pip install typesafe-sdk`.
Jev Repos & SDKs#official#sdk#python
The generated API reference for the official Python SDK, split into sync and async clients, question and response types, retries, exceptions, and constants.
Jev Repos & SDKsDocs#official#docs#sdk
Official
AsyncTypeSafeClient: async system_one calls, model listing, per-call retry and timeout overrides, and an optional Pydantic response model.
Jev Repos & SDKsDocs#official#docs#sdk
Official
Release notes for the official Python SDK, from the 14 September 2026 public release through the Pydantic migration and the HTTP/2 extra.
Jev Repos & SDKsDocs#official#docs#sdk
Official
Usage guide for typesafe-sdk: sync and async clients, uv and pip installs, and one request mixing Noul, Choice, and Score questions with answers grouped by type.
Jev Repos & SDKsDocs#official#docs#sdk
Official
The Python SDK error hierarchy: HTTP status classes, rate-limit wait hints, connection and timeout errors, and response-body validation failures.
Jev Repos & SDKsDocs#official#docs#sdk
Official
How the Python SDK types questions: Noul, Choice, and Score as objects or dictionaries, including optional JSON instructions and criteria.
Jev Repos & SDKsDocs#official#docs#sdk
Official
The Python response models: SystemOneResponse groups answers by id, and records the model name, token usage, and request id.
Jev Repos & SDKsDocs#official#docs#sdk
Official
RetryPolicy for the Python SDK: attempt count, exponential backoff with jitter, which HTTP statuses retry, and a total time budget.
Jev Repos & SDKsDocs#official#docs#sdk
Official
How to shape what Jev evaluates: a string, a named object, or an array of messages. Every question in a request sees the same state and is evaluated independently.
Jev Practices & PatternsDocs#official#docs#state
Official
Back to all articles

Related Jev articles

Workers AI, Vercel, and validation: how the TypeScript SDK behaves at the edge, what it returns, and where to put the schema check.
Jev Repos & SDKs#sdk#typescript#edge
Read article
.NET, Java, Rust, Go, PHP, and Swift clients: what exists, what the unofficial ports cover, and how to verify a client before adopting it.
Jev Repos & SDKs#sdk#polyglot#integrations
Read article

From the Jev AI model community

Posts from builders shipping with Jev right now.

Follow @typesafeai