Skip to content

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 400 through 529 each 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 ​

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:

http
POST https://api.typesafe.ai/v1/systemone
Authorization: Bearer <你的API Key>
Content-Type: application/json

Plus an auxiliary endpoint for "checking which models are available":

bash
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):

bash
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"
    }
  }
}
JSON

The whole API has just this one shape. Below, we break it down block by block for quick reference.

Full table of request fields ​

FieldRequiredTypeDescription
model✅StringModel name or alias; available names are returned by GET /v1/models. E.g., jev-latest, jev-preview
state✅String / JSON object / text arrayThe 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:

FieldRequiredDescription
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
instructionsAll four question types can be omitted in the schemaThe 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
criteriaRequired for Choice and ScoreChoice: 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 typetype Valuecriteria formAnswer fields
Choicechoicea map of options, 1–255 of themchoice (the name of the selected option, i.e. the highest-probability one), confidence, probabilities (one probability per option, summing non-strictly to 1)
Scorescorean ordered list of levels, 1–10 itemsscore (probability-weighted expected value, can fall between two levels, e.g. 0.67 / 1.7), confidence, legend (a mapping from levels to values), probabilities
Noulnouloptional: definitions of true/falsenoul (the true/yes probability of 0–1; near 0.5 = uncertain; no confidence field)
Boxboxnonebox (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:

Full table of request headers ​

Request headerWhen to useDescription
Authorization: Bearer <key>Every requestMissing or invalid → 401
Content-Type: application/jsonText requestsImages go via multipart/form-data: JSON as the first part (named request), one separate part per image
Content-Encoding: gzip or x-gzip / zstdCompressing large request bodiesOnly one layer allowed; zstd window ≤ 8 MiB; unsupported or multi-layer → 415, corrupted compressed body → 400
TypeSafe-Danger-Zone-Images: trueWhen the request contains images or Box questionsMust be included with every request containing images; omitted → 400
TypeSafe-Danger-Zone-Long-Context: trueWhen long text doesn't fit in the default contextBoth 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:

RuleLimitViolation status code
Request body size≤ 32 MiB (excluding headers)413 (when exceeded, counted both before and after compression)
EncodingUTF-8 JSON, no BOM400
Nestingstate / instructions / criteria each ≤ 64 levels of objects and arrays; flatten anything deeper first400
NumbersOnly finite numbers are accepted: values like NaN, Infinity, 1e400 are all rejected; pass large numbers and IDs as strings400

Compression adds only two more rules: a single layer only, and zstd window ≤ 8 MiB.

Full table of response fields ​

json
{
  "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.)

FieldLocationDescription
modelTop levelName 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)
answersTop levelMatched one-to-one against the question IDs you assigned; each answer's type matches its question's type
usage.input_tokensTop levelNumber of input tokens billed for this request (billing counts input only)
usage.output_tokensTop levelNumber of output tokens, currently free

Full table of status codes ​

Status codeMeaningWhat you should do
200SuccessRetrieve answers by ID
400 Bad RequestViolates request rules (invalid UTF-8, nesting beyond 64 levels, non-finite numbers, corrupted compression body, missing image header); max_tokens_exceeded = context length exceededFix the request body against the "request rules" table; if the context is exceeded, slim down the state or enable the long-context header
401 UnauthorizedKey missing or invalidCheck the Authorization header; SDK users should check TYPESAFE_API_KEY (see the pitfall reminder below)
413 Payload Too LargeRequest body exceeds 32 MiBReduce the state; compression won't save you 413 — 32 MiB applies both before and after compression
415 Unsupported Media TypeContent-Encoding unsupported or multiple compression layersUse only one of gzip / x-gzip / zstd, single layer
422 Unprocessable EntityRequest body validation failed: missing required fields, malformed questions, etc.Check the detail array in the body to locate the issue
429 Too Many RequestsRate 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 OverloadedTypeSafe temporarily overloadedSame as 429: retry after backoff

The body of 422 pinpoints the exact field:

json
{
  "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):

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:

python
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:

VariablePurposeDefault
TYPESAFE_API_KEYAPI Key (required)—
TYPESAFE_BASE_URLAPI root URLhttps://api.typesafe.ai
TYPESAFE_DEFAULT_MODELDefault modeljev-latest
TYPESAFE_LOG_LEVELtypesafe_sdk Log level, takes effect once at importNot 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:

FieldRecommended valueNotes
modeljev-latestAlias, currently pointing to Jev 1.14; to pin a version, write jev-1.14.0
stateString / JSON object / text arrayAny of the three forms
questions{ID: {type, instructions, criteria}}IDs are not sent to the model; put full questions in instructions
Context64k / request · 32k / questionBoth become 262k with the long-context header
Request bodyUTF-8 JSON ≤ 32 MiB · nesting ≤ 64 levels · numbers must be finiteOne 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.0 and jev-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 ​