API quick reference: requests, responses, and errors
From the quick start to real-world scenarios, you have been dealing with one and the same thing: the TypeSafe API's /v1/systemone endpoint. This page pulls it out on its own and compresses it into a single quick-reference table—request fields, question-type answers, request headers, request rules, status codes, all in one lookup. Keep this page open while coding; it's faster than flipping through the official API docs.
What you'll be able to do after this lesson
- Name the TypeSafe API's two endpoints, the authentication method, and the four request body fields (model / state / questions / assets)
- Look up the exact return fields for each type of answer by question type (Choice / Score / Noul / Box)
- Locate errors using the status code table: what
400through529each mean and what to change - Know when to add the
TypeSafe-Danger-Zone-*request headers and when to write your own retries - Use the SDK's equivalent-method mapping table to make the same calls in Python / JavaScript
Your current predicament
- Every time you write code you have to flip back to the quick start lesson to copy the request structure—there's no single page you can just look it up on
- When an error occurs you only know the status code number, not whether the request body is broken or the Key is wrong
- The direct-curl and SDK ways of writing it clash in your head, and the field names don't match up
When to use this technique
When you need to:
- One page to keep at hand, checking the TypeSafe API's fields and status codes as you write
- Quickly judge when debugging: "is this 4xx my problem or the service's problem?"
And you don't want to:
- Dig through the long official API reference docs every time
- Jump back and forth between multiple tutorial pages to find the same field description
🎒 Preparation before starting
- You've gotten your first request working: install the Python SDK and make your first request (or connect directly with curl)
- API Key is in the environment variable
TYPESAFE_API_KEY - If it won't install or run, first go to quick start troubleshooting—this page is only a quick reference, not a step-by-step troubleshooting guide
Core idea: one endpoint, everything starts from the request structure
All Jev series models are served by the same endpoint: POST https://api.typesafe.ai/v1/systemone. The model field in the request determines which model handles it—there is no such thing as "one URL per model".
Authentication recognizes only one header:
POST https://api.typesafe.ai/v1/systemone
Authorization: Bearer <你的API Key>
Content-Type: application/jsonPlus an auxiliary endpoint for "checking which models are available":
curl https://api.typesafe.ai/v1/models \
-H "Authorization: Bearer $TYPESAFE_API_KEY"You should see: a models array, each item containing name, description, release_date (with example values like jev-latest, General-purpose system one model., 2026-09-15). Put the name you get directly into the request's model field.
What does the request body look like? Here's the ticket example from the official quick start (direct curl version, two questions excerpted; requires curl 7.76+ with --fail-with-body so the response body is printed even on errors):
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'
{
"state": "Our API integration started returning 500 errors...",
"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"
}
}
}
JSONThe whole API has just this one shape. Below, we break it down block by block for quick reference.
Full table of request fields
| Field | Required | Type | Description |
|---|---|---|---|
model | ✅ | String | Model name or alias; available names are returned by GET /v1/models. E.g., jev-latest, jev-preview |
state | ✅ | String / JSON object / text array | The content evaluated jointly across all questions. For the three forms, see State |
questions | ✅ | object (at least 1 question) | Keys are question IDs you choose yourself, values are question objects; there is no upper limit on the number of questions, the only limit is context length |
assets | ❌ | object (at least 1 entry) | Image assets, keyed by placeholder (the official recommendation is one UUID per image); when using images you must pair them with the TypeSafe-Danger-Zone-Images: true header |
Fields of the question object:
| Field | Required | Description |
|---|---|---|
| ID (the object's key) | ✅ | Chosen by you, used to match up answers in the response; it is not sent to the model—the full question must be written in instructions, even if the ID seems self-explanatory |
type | ✅ | choice / score / noul / box pick one of the four |
instructions | All four question types can be omitted in the schema | The complete question; can be a string, JSON object, or array (the question and the data it references can be kept separate). The ID is not sent to the model, so in practice you should write it for every question |
criteria | Required for Choice and Score | Choice: a map of options (1–255 of them); Score: an ordered list of levels (1–10 items); Noul: optional true/false true/false definitions |
To reference a state field in instructions, wrap the dot/bracket path in backticks, e.g. `ticket.messages[0].text`—see the State lesson for details.
Full table of question types and answer fields
You can mix any question types within one request; all questions are evaluated in parallel and independently against the same state (see parallel questions for an extreme example of this usage).
| Question type | type Value | criteria form | Answer fields |
|---|---|---|---|
| Choice | choice | a map of options, 1–255 of them | choice (the name of the selected option, i.e. the highest-probability one), confidence, probabilities (one probability per option, summing non-strictly to 1) |
| Score | score | an ordered list of levels, 1–10 items | score (probability-weighted expected value, can fall between two levels, e.g. 0.67 / 1.7), confidence, legend (a mapping from levels to values), probabilities |
| Noul | noul | optional: definitions of true/false | noul (the true/yes probability of 0–1; near 0.5 = uncertain; no confidence field) |
| Box | box | none | box (top/bottom/left/right four edges, as proportions of the image height/width 0–1), confidence (can be null). Requires an image + the image header enabled |
How to choose a question type, and the full usage of each:
- Choice · Score · Noul yes/no questions · Box and images
- How confidence is computed and how to set thresholds: Confidence
Full table of request headers
| Request header | When to use | Description |
|---|---|---|
Authorization: Bearer <key> | Every request | Missing or invalid → 401 |
Content-Type: application/json | Text requests | Images go via multipart/form-data: JSON as the first part (named request), one separate part per image |
Content-Encoding: gzip or x-gzip / zstd | Compressing large request bodies | Only one layer allowed; zstd window ≤ 8 MiB; unsupported or multi-layer → 415, corrupted compressed body → 400 |
TypeSafe-Danger-Zone-Images: true | When the request contains images or Box questions | Must be included with every request containing images; omitted → 400 |
TypeSafe-Danger-Zone-Long-Context: true | When long text doesn't fit in the default context | Both per-request and per-question context become 262k; the price stays the same, but it is slower and less accurate, so use it only for requests that need it |
Default context limits (same for Jev 1.14 and 1.13): 64k per request / 32k per question tokens; the number of questions itself has no cap. For a quick reference on prices and model versions, see Models and Pricing.
Request rules and compression
The official errors documentation sets four hard rules for every request:
| Rule | Limit | Violation status code |
|---|---|---|
| Request body size | ≤ 32 MiB (excluding headers) | 413 (when exceeded, counted both before and after compression) |
| Encoding | UTF-8 JSON, no BOM | 400 |
| Nesting | state / instructions / criteria each ≤ 64 levels of objects and arrays; flatten anything deeper first | 400 |
| Numbers | Only finite numbers are accepted: values like NaN, Infinity, 1e400 are all rejected; pass large numbers and IDs as strings | 400 |
Compression adds only two more rules: a single layer only, and zstd window ≤ 8 MiB.
Full table of response fields
{
"model": "jev-1.14.0",
"answers": {
"department": {
"type": "choice",
"choice": "technical",
"confidence": 1.0,
"probabilities": { "technical": 1.0, "billing": 0.0, "sales": 0.0 }
},
"frustration": {
"type": "score",
"score": 0.67,
"confidence": 0.5,
"legend": { "0": "Calm…", "1": "Frustrated…", "2": "Very angry…" },
"probabilities": { "0": 0.33, "1": 0.67, "2": 0.0 }
},
"is_urgent": { "type": "noul", "noul": 0.99 }
},
"usage": { "input_tokens": 428, "output_tokens": 73 }
}(Example values; they change with versions. For a complete example, see the first request lesson.)
| Field | Location | Description |
|---|---|---|
model | Top level | Name of the model that actually answered; it may differ from the alias in your request (e.g., you request jev-latest and get back a specific version number) |
answers | Top level | Matched one-to-one against the question IDs you assigned; each answer's type matches its question's type |
usage.input_tokens | Top level | Number of input tokens billed for this request (billing counts input only) |
usage.output_tokens | Top level | Number of output tokens, currently free |
Full table of status codes
| Status code | Meaning | What you should do |
|---|---|---|
200 | Success | Retrieve answers by ID |
400 Bad Request | Violates request rules (invalid UTF-8, nesting beyond 64 levels, non-finite numbers, corrupted compression body, missing image header); max_tokens_exceeded = context length exceeded | Fix the request body against the "request rules" table; if the context is exceeded, slim down the state or enable the long-context header |
401 Unauthorized | Key missing or invalid | Check the Authorization header; SDK users should check TYPESAFE_API_KEY (see the pitfall reminder below) |
413 Payload Too Large | Request body exceeds 32 MiB | Reduce the state; compression won't save you 413 — 32 MiB applies both before and after compression |
415 Unsupported Media Type | Content-Encoding unsupported or multiple compression layers | Use only one of gzip / x-gzip / zstd, single layer |
422 Unprocessable Entity | Request body validation failed: missing required fields, malformed questions, etc. | Check the detail array in the body to locate the issue |
429 Too Many Requests | Rate limit exceeded (per organization: tokens/s + requests/s; check console.typesafe.ai/rate-limits in the console) | Retry with exponential backoff; the SDK handles it automatically by default |
529 Overloaded | TypeSafe temporarily overloaded | Same as 429: retry after backoff |
The body of 422 pinpoints the exact field:
{
"detail": [
{ "loc": ["body", "state"], "msg": "Field required", "type": "missing" }
]
}Find the offending field via loc; msg tells you what is missing.
Error handling and retries
The official rule in one sentence: when you receive 429 or 529, retry with exponential backoff (lengthening the wait each time); do not immediately resend as-is. When the response carries a retry-after header, follow it.
If you use the SDK: the Python / JavaScript SDKs ship with a default retry policy that handles 429 automatically, no extra code needed. To customize (Python):
from typesafe_sdk import RetryPolicy, TypeSafeClient
client = TypeSafeClient(retry=RetryPolicy(max_retries=3, backoff_max=0.2, timeout=1.0))The standard way to catch API errors in the Python SDK:
from typesafe_sdk import TypeSafeAPIError
try:
client.system_one(state, questions)
except TypeSafeAPIError as error:
print(error.status, error.request_id)error.status is the status code, and error.request_id is the request identifier——include it when reporting to official support.
If you call the API directly: you have to write retries yourself. curl's --fail-with-body at least ensures you can see the response body on errors.
Quick notes on the two Python clients
TypeSafe API has identically named methods on both Python clients: TypeSafeClient (synchronous) and AsyncTypeSafeClient (asynchronous), both being system_one(). Environment variables the SDK reads:
| Variable | Purpose | Default |
|---|---|---|
TYPESAFE_API_KEY | API Key (required) | — |
TYPESAFE_BASE_URL | API root URL | https://api.typesafe.ai |
TYPESAFE_DEFAULT_MODEL | Default model | jev-latest |
TYPESAFE_LOG_LEVEL | typesafe_sdk Log level, takes effect once at import | Not set |
TypeScript Answer Cheat Sheet
In the JS/TS SDK (@typesafe-ai/sdk), answers are fetched directly by ID: response.answers.department.choice, and the types are inferred from the question definitions (e.g., "billing" | "technical" | "sales").
Pitfall Reminders
❌ You request jev-latest, but the model in the response is not it
This is not an error—the model in the response is the name of the model that actually answered, which may differ from the alias in the request (the official API spec explicitly states this). For the current alias targets, see Models and Pricing.
❌ Spaces or newlines sneak into your Key
With direct connections, such Keys keep getting 401. The SDK strips leading and trailing whitespace from the Key (including newlines read from a file), but internal spaces, control characters, and non-ASCII characters are rejected outright (the error is raised at client creation, never reaching 401); an explicitly passed empty Key will not fall back to the environment variable. When copying a Key from the console, don't accidentally select an extra space.
❌ Guessing what went wrong from the 422 status code
Don't guess—read the body: in the detail array, loc is the field path (e.g., ["body", "questions", "urgency", "criteria"]), and msg is the reason.
❌ Can't find confidence in Noul answers
It wasn't lost—Noul naturally has no confidence field. To apply a uniform threshold, compute |2p − 1| yourself; the official suggested starting points: yes ≥ 0.7, no ≤ 0.3 (see Noul Yes/No Questions).
❌ High-frequency direct API calls flooded with 429
Rate limits are counted per organization (tokens/s + requests/s). The SDK retries with backoff by default; for direct connections, write your own exponential backoff. For higher quotas, use the official request form (support.typesafe.ai).
Lesson Summary
✅ One endpoint POST /v1/systemone, one auth header, four fields in the request body (model / state / questions / assets).
✅ Four question types, four kinds of answers: Choice returns a choice + probabilities + confidence, Score returns an expected score + legend, Noul returns only a 0–1 probability, and Box returns four edge ratios.
✅ Status codes in one sweep: 400 rules / 401 Key / 413 size / 415 compression / 422 validation (read the body) / 429 529 retry with backoff.
✅ Add the two Danger Zone headers as needed: TypeSafe-Danger-Zone-Images for images, TypeSafe-Danger-Zone-Long-Context for long contexts.
Key configuration fields recap:
| Field | Recommended value | Notes |
|---|---|---|
model | jev-latest | Alias, currently pointing to Jev 1.14; to pin a version, write jev-1.14.0 |
state | String / JSON object / text array | Any of the three forms |
questions | {ID: {type, instructions, criteria}} | IDs are not sent to the model; put full questions in instructions |
| Context | 64k / request · 32k / question | Both become 262k with the long-context header |
| Request body | UTF-8 JSON ≤ 32 MiB · nesting ≤ 64 levels · numbers must be finite | One layer of gzip / zstd compression allowed |
Next Lesson Preview
Now that you can call the API, let's get the billing straight: Models and Pricing: versions, context limits, rates.
You will learn:
- Version timelines and alias targets for
jev-1.14.0andjev-1.13.0 - How billing works at $42/Btok: why only input tokens are counted
- Details of context limits and the cost of long contexts
- How to check rate limits and how to request a quota increase
Related Tutorials
- Can't install or run? Getting-started troubleshooting—the full version, debugging error by error
- Install the Python SDK and send your first request—a step-by-step walkthrough of the request structure
- Alternative installation methods: uv, npm, and calling the API directly with curl—the complete walkthrough for direct curl calls