HTTP API: POST /v1/systemone and GET /v1/models
TL;DR
POST https://api.typesafe.ai/v1/systemonewithAuthorization: Bearer <API_KEY>,Content-Type: application/json, and a body of{state, model, questions}. You get back{model, answers, usage}whereanswersis keyed by the question ids you chose.GET /v1/modelslists the model names your key may send.
Endpoints
| Method | URL | Purpose | Request schema | 200 schema | 422 schema |
|---|---|---|---|---|---|
POST |
https://api.typesafe.ai/v1/systemone |
Evaluate state against a map of typed questions |
SystemOneRequest |
SystemOneResponse |
HTTPValidationError |
GET |
https://api.typesafe.ai/v1/models |
List models and aliases available to the account | — | ModelMetadataList |
HTTPValidationError |
OpenAPI operationIds: systemone_v1_systemone_post, models_v1_v1_models_get (raw/site/openapi.json). See OpenAPI component schemas for every component schema.
Authentication
| Header | Value | Required | Notes |
|---|---|---|---|
Authorization |
Bearer <API_KEY> |
Yes | OpenAPI security scheme HTTPBearer (type: http, scheme: bearer) applies to both endpoints. A missing or invalid key returns 401 Unauthorized. |
Content-Type |
application/json |
Yes for POST |
The only request content type in the spec. |
Get a key at https://console.typesafe.ai/settings/keys (raw/docs/introduction__quickstart.md). The SDKs read it from TYPESAFE_API_KEY; see TYPESAFE_* environment variables across SDKs.
Headers the official SDKs also send or read (raw/github/typesafe-sdk-python/src/typesafe_sdk/_core/constants.py, raw/github/typesafe-sdk-js/src/client.ts): request User-Agent: typesafe-sdk/<version>, Accept, X-TypeSafe-SDK, X-TypeSafe-Runtime, X-TypeSafe-Retry-Count; response x-typesafe-request-id (surfaced as request_id / requestId on errors), retry-after, retry-after-ms. None of these are documented as required in raw/docs/api.md.
Request body (SystemOneRequest)
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
state |
string | object | array |
Yes | — | The content all questions in this request refer to. A plain string for text, or structured data (object/array) for chat logs, records, or application state. |
model |
string |
Yes | — | The model that handles the request. Use "jev-latest". Names come from GET /v1/models; versioned IDs such as jev-1.13.0 are also accepted. |
questions |
map<string, Question> |
Yes | minProperties: 1 (raw/site/openapi.json) |
A map of typed question objects. You choose each key; answers come back under the same keys. The key is not sent to the underlying model and is not used in inference (raw/docs/api.md). |
state and the questions share one budget: 64k tokens per request, and 32k tokens for state plus the single longest question (raw/docs/models.md). See Models, aliases, pricing, rate limits, context.
Minimal example request (verbatim, raw/docs/api.md)
{
"state": "Help! My payouts have been failing for 3 days.",
"model": "jev-latest",
"questions": {
"is_urgent": {
"type": "noul",
"instructions": "Does this convey urgency?"
}
}
}
Question types
A Question is one of three types, selected by its type field. All three share type and instructions; each adds its own criteria. The OpenAPI discriminator is type with mapping noul → NoulQuestion, choice → ChoiceQuestion, score → ScoreQuestion.
Common fields
| Field | Type | Required | Description |
|---|---|---|---|
type |
"noul" | "choice" | "score" |
Yes (all three types) | Selects the question type; the answer carries the same type. |
instructions |
string | object | array | null |
Marked required in raw/docs/api.md for all three types; not listed in required in raw/site/openapi.json (nullable there). See Source disagreements. |
What the model should decide, rate, or answer yes/no about. |
Noul (type: "noul")
A yes/no question. Returns the probability the answer is yes.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"noul" |
Yes | Identifies a yes/no question or statement. |
instructions |
string | object | array |
Yes (api.md) | The yes/no question or statement to evaluate. |
criteria |
NoulCriteria | null |
No | Optional descriptions of what a yes and a no mean. |
criteria.true |
string | object | array | null |
No | What a yes (value near 1) means. |
criteria.false |
string | object | array | null |
No | What a no (value near 0) means. |
{
"state": "Help! My payouts have been failing for 3 days.",
"model": "jev-latest",
"questions": {
"is_urgent": {
"type": "noul",
"instructions": "Does this convey urgency?",
"criteria": {
"true": "Explicitly time-sensitive",
"false": "No urgency expressed"
}
}
}
}
Choice (type: "choice")
Picks one option from a set you define. Returns the chosen option and the full probability distribution.
| Field | Type | Required | Description |
|---|---|---|---|
type |
"choice" |
Yes | Identifies a question that selects one of the choices in criteria. |
instructions |
string | object | array |
Yes (api.md) | What the model should decide. |
criteria |
map<string, string | object | array | null> |
Yes | Option name → rubric description. Use null when an option needs no extra detail; a choice without a description is interpreted by its name alone. |
{
"state": "Help! My payouts have been failing for 3 days.",
"model": "jev-latest",
"questions": {
"department": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {
"billing": "Payments, invoicing, refunds",
"technical": "Bugs, outages, integrations",
"sales": "Pricing, upgrades, new accounts"
}
}
}
}
Score (type: "score")
Rates the state along a rubric you define. Returns a probability-weighted value across your levels.
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
type |
"score" |
Yes | — | Identifies a question that rates the content using the levels in criteria. |
instructions |
string | object | array |
Yes (api.md) | — | What the model should rate. |
criteria |
array<string | object | array> |
Yes | minItems: 1 (raw/site/openapi.json); "at least two levels" (raw/docs/api.md) |
Ordered descriptions of the score levels. Each description's position determines its score, starting at zero. |
{
"state": "Help! My payouts have been failing for 3 days.",
"model": "jev-latest",
"questions": {
"frustration": {
"type": "score",
"instructions": "How frustrated is the customer?",
"criteria": ["Calm", "Frustrated", "Very angry"]
}
}
}
Response body (SystemOneResponse)
| Field | Type | Required | Constraints | Description |
|---|---|---|---|---|
model |
string |
Yes | — | The model that performed the evaluation. May differ from the alias supplied in the request — log it to know which version answered. |
answers |
map<string, Answer> |
Yes | minProperties: 1 |
One answer per question, keyed by the same ids you used in questions. Each answer's type matches its question's type. |
usage |
Usage |
Yes | — | Token usage for this evaluation. |
usage.input_tokens |
integer |
Yes | — | Billable input tokens used to evaluate the request. |
usage.output_tokens |
integer |
Yes | — | Output tokens used to answer. Output tokens are currently free of charge (raw/site/openapi.json, raw/docs/models.md). |
Answer types
Every answer carries a type matching its question. Choice and Score answers also carry a confidence between 0 and 1, derived from the answer's probability distribution (raw/docs/api.md). Discriminator mapping: noul → NoulAnswer, choice → ChoiceAnswer, score → ScoreAnswer.
Noul answer
| Field | Type | Required | Description |
|---|---|---|---|
type |
"noul" |
Yes | Identifies a yes/no answer. |
noul |
number |
Yes | The yes/no answer on a scale from 0 (no) to 1 (yes). Near 0.5 indicates uncertainty. |
A Noul answer carries no confidence field.
Choice answer
| Field | Type | Required | Description |
|---|---|---|---|
type |
"choice" |
Yes | Identifies a selection from the requested choices. |
choice |
string |
Yes | The name of the highest-probability option among the question's criteria. |
probabilities |
map<string, number> |
Yes | Every option mapped to its probability, 0 to 1; values sum to approximately 1. |
confidence |
number |
Yes | How certain the model is, from 0 to 1, derived from probabilities. |
Score answer
| Field | Type | Required | Description |
|---|---|---|---|
type |
"score" |
Yes | Identifies a rating against the requested score levels. |
score |
number |
Yes | Probability-weighted average of the rubric levels; may fall between integer levels. |
legend |
map<string, string | object | array> |
Yes | Each level number (string key) mapped back to its criteria description. |
probabilities |
map<string, number> |
Yes | Each level (string key, same keys as legend) mapped to its probability; values sum to approximately 1. |
confidence |
number |
Yes | How certain the model is, from 0 to 1, derived from probabilities. |
Verbatim example response (raw/docs/api.md)
{
"model": "jev-latest",
"answers": {
"is_urgent": {
"type": "noul",
"noul": 0.92
}
},
"usage": { "input_tokens": 312, "output_tokens": 48 }
}
Choice answer (raw/docs/api.md):
{
"model": "jev-latest",
"answers": {
"department": {
"type": "choice",
"choice": "technical",
"probabilities": { "billing": 0.08, "technical": 0.85, "sales": 0.07 },
"confidence": 0.82
}
},
"usage": { "input_tokens": 312, "output_tokens": 48 }
}
Score answer (raw/docs/api.md):
{
"model": "jev-latest",
"answers": {
"frustration": {
"type": "score",
"score": 1.6,
"legend": { "0": "Calm", "1": "Frustrated", "2": "Very angry" },
"probabilities": { "0": 0.05, "1": 0.3, "2": 0.65 },
"confidence": 0.78
}
},
"usage": { "input_tokens": 312, "output_tokens": 48 }
}
cURL example (verbatim, raw/docs/introduction__quickstart.md)
curl -X POST https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d @- <<'EOF'
{
"state": "Hi, I've been trying to connect my Stripe account for 3 days and it keeps failing. I'm losing sales. Please help ASAP.",
"model": "jev-latest",
"questions": {
"urgency": {
"type": "noul",
"instructions": "Does this message express urgency?"
}
}
}
EOF
Mixed-type request and response (verbatim, raw/docs/introduction__quickstart.md)
{
"state": "Hi, I've been trying to connect my Stripe account for 3 days and it keeps failing. I'm losing sales. 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"
}
},
"frustration": {
"type": "score",
"instructions": "How frustrated the customer appears",
"criteria": [
"Calm, just stating facts",
"Frustrated but civil",
"Very angry, strong language"
]
},
"is_urgent": {
"type": "noul",
"instructions": "The message conveys urgency or time-sensitivity"
}
}
}
{
"model": "jev-latest",
"answers": {
"department": {
"type": "choice",
"choice": "billing",
"probabilities": {
"billing": 0.84,
"technical": 0.159,
"sales": 0.001
},
"confidence": 0.596
},
"frustration": {
"type": "score",
"score": 1.035,
"legend": {
"0": "Calm, just stating facts",
"1": "Frustrated but civil",
"2": "Very angry, strong language"
},
"confidence": 0.842
},
"is_urgent": {
"type": "noul",
"noul": 0.999
}
},
"usage": {
"input_tokens": 312,
"output_tokens": 48
}
}
Note: this quickstart response omits probabilities on the Score answer, although both raw/docs/api.md and raw/site/openapi.json mark it required. Treat the quickstart sample as abridged (inferred).
GET /v1/models
Returns the names your account can send in the model field, with a description and release date for each. It currently lists the aliases. Versioned IDs such as jev-1.13.0 are accepted by the model field whether or not they appear in the list (raw/docs/models.md).
curl https://api.typesafe.ai/v1/models \
-H "Authorization: Bearer $TYPESAFE_API_KEY"
Response (ModelMetadataList):
| Field | Type | Required | Description |
|---|---|---|---|
models |
array<ModelMetadata> |
Yes | One entry per model or alias. |
models[].name |
string |
Yes | The model ID or alias, as accepted by the model field. |
models[].description |
string |
Yes | What the model is for. |
models[].release_date |
string |
Yes | Release date, formatted YYYY-MM-DD. |
Example from the OpenAPI examples (raw/site/openapi.json):
{
"models": [
{
"name": "jev-latest",
"description": "General-purpose system one model.",
"release_date": "2026-09-15"
}
]
}
Errors
Errors use standard HTTP status codes with a JSON body describing what went wrong (raw/docs/api.md).
| Status | Meaning |
|---|---|
400 Bad Request |
The request was invalid. Not listed in raw/docs/api.md; both SDKs map it (BadRequestError / TypeSafeBadRequestError). Not retryable. |
401 Unauthorized |
Missing or invalid API key. Check the Authorization header. |
403 Forbidden |
Access was denied. Not listed in raw/docs/api.md; both SDKs map it (PermissionDeniedError / TypeSafePermissionDeniedError). Not retryable. |
404 Not Found |
The resource was not found. Not listed in raw/docs/api.md; both SDKs map it (NotFoundError / TypeSafeNotFoundError). Check the path and TYPESAFE_BASE_URL. |
408 Request Timeout |
Not described in the docs; present only in the SDK retry defaults (http_statuses / httpStatuses include 408). Retryable. |
422 Unprocessable Entity |
The request body failed validation — for example a missing required field or a malformed question. The body details the offending field (HTTPValidationError). |
429 Too Many Requests |
You have exceeded your rate limit. Back off and retry after a short delay. |
5xx (500–599) |
The server failed to process the request. Not listed as a range in raw/docs/api.md; both SDKs map the whole range to InternalServerError / TypeSafeInternalServerError. Retryable. |
529 Overloaded |
TypeSafe is temporarily overloaded. Retry after a short delay. (Handled by the 5xx branch in both SDKs; neither has a dedicated class.) |
This list is not exhaustive; the SDK-level mapping incl. 400/403/404/408/5xx is in HTTP status codes, rate limits, retry semantics. The 422 body is {"detail": [ValidationError, ...]}; each entry has loc, msg, type, and optionally input and ctx. Full shape in OpenAPI component schemas. For the complete status → SDK exception → retry mapping see HTTP status codes, rate limits, retry semantics.
Handling rate limits
Verbatim guidance (raw/docs/api.md): "When you receive a 429 Too Many Requests or 529 Overloaded response, retry the request with exponential backoff instead of retrying immediately. Our client SDKs handle this automatically, so no extra handling is needed if you use one of our SDKs with its default retry policy."
Additional facts for direct HTTP callers:
- Limits are 250,000 tokens per second and 1,200 requests per minute; exceeding either returns
429(raw/docs/models.md). - The SDKs "honor the
retry-afterheader when the response carries one" (raw/docs/models.md). The SDK implementations also readretry-after-ms, preferring it overretry-after(raw/github/typesafe-sdk-js/src/retry.ts, raw/github/typesafe-sdk-python/src/typesafe_sdk/_core/errors.py). If you implement your own client, read both. - Reference backoff, matching the SDK defaults: initial 500 ms, doubling, capped at 5,000 ms, 25% jitter, 2 retries after the initial attempt; cap any honored server delay at 60,000 ms before falling back to backoff (raw/github/typesafe-sdk-js/src/retry.ts).
- Rate limits are stated to be "adjusting dynamically" and can change without notice; higher limits are on custom and enterprise plans via sales@typesafe.ai (raw/docs/models.md).
Source disagreements
| Point | raw/docs/api.md | raw/site/openapi.json | Guidance |
|---|---|---|---|
instructions requiredness |
Marked required on Noul, Choice, and Score questions |
Not in any question's required list; typed anyOf [string, object, array, null] |
Always send instructions; the server schema tolerates its absence but the docs treat it as mandatory. |
Score criteria minimum |
"You must include at least two levels" | minItems: 1 |
Send at least two levels. |
Score answer probabilities |
Required | Required | The quickstart example (raw/docs/introduction__quickstart.md) omits it; the two contract sources agree it is present. |
Version notes
- OpenAPI document version
0.2.0, OpenAPI spec version3.1.0(raw/site/openapi.json). - This is the v1 API. The preview endpoint
POST /preview/evaluationis replaced byPOST /v1/systemone; field names changed. See Migrating from /preview/evaluation to /v1/systemone. documentis no longer accepted; a request withdocumentfails validation (raw/docs/migrating-to-v1.md).
Related
- OpenAPI component schemas — every component schema, field by field
- Models, aliases, pricing, rate limits, context — model IDs, aliases, price, limits, context
- HTTP status codes, rate limits, retry semantics — status → exception → retry table
- Migrating from /preview/evaluation to /v1/systemone — what changed from
/preview/evaluation - TYPESAFE_* environment variables across SDKs —
TYPESAFE_API_KEYand friends - Primitives: Choice, Score, Noul — what Choice, Score, and Noul mean
- Python SDK: install, clients, system_one() — the typed Python client over this wire format
- JavaScript/TypeScript SDK: install, client, choice/score/noul — the typed JS/TS client over this wire format
- Quickstart: first call in HTTP, Python, JS — first call end to end
Sources
- raw/docs/api.md (https://docs.typesafe.ai/api)
- raw/site/openapi.json (https://docs.typesafe.ai/openapi.json)
- raw/docs/introduction__quickstart.md (https://docs.typesafe.ai/introduction/quickstart)
- raw/docs/models.md (https://docs.typesafe.ai/models)
- raw/docs/migrating-to-v1.md (https://docs.typesafe.ai/migrating-to-v1)
- raw/github/typesafe-sdk-js/src/retry.ts, raw/github/typesafe-sdk-python/src/typesafe_sdk/_core/constants.py