SYSTEMONE
System One
Ask named questions about a state and get one calibrated answer per question: a probability, a choice or a score. Nothing is generated.
Authorization
AuthorizationstringheaderrequiredBearer token — your API key. Example: Bearer sk-...
System One is a decision API. You send a state (some text, an object
or a list) and a set of named questions about it, and get back one answer per
question: the probability of yes, the most likely of several options, or a level
on a scale, each with the full distribution behind it. No text is generated, so
the answers are always well-formed and there is no output to bill.
Use it where you would otherwise prompt a chat model and parse its reply: routing and triage, extraction checks, policy decisions, scoring. One request can ask many questions about the same state, and every question sees the whole state.
The request and response follow TypeSafe's System One API. Code that calls
TypeSafe's POST /v1/systemone works here with an API key from this platform and
this API's host in place of TypeSafe's. The differences are listed at the end of
this page.
Models
| Model | Question types | Images | Context per request |
|---|---|---|---|
apex | noul, choice, score | yes | 65,536 tokens |
Questions
Each entry in questions is { type, instructions?, criteria? }. The name you
give it is the key its answer comes back under.
type | criteria | Answer |
|---|---|---|
noul | Optional { "true": "…", "false": "…" } describing the two outcomes (default "yes" and "no") | noul: the probability of yes |
choice | Required: an object of 1 to 255 options, name → description (or null to use the name) | choice: the most likely option, with confidence and every option's probabilities |
score | Required: an array of 1 to 10 level descriptions, lowest first | score: the expected level, with confidence and each level's probabilities |
instructions is the question itself. Leave it out when the criteria say it all,
for example a choice whose options are self-explanatory.
A score is an expected value, not a pick. In the example, a score of
1.79 on four levels means "close to level 2, leaning towards 1", worked out as
each level's index times its probability, summed. When you need a single level, take the key of the largest
entry in probabilities. confidence is that entry's probability.
State
state can be a string, an object or an array. It is read as text:
- a string as it is;
- an object as one
key: valueline per key, with a list of strings under a key written one- itemper line; - an array as one
- itemline per element.
Nested values are written as JSON. Name your keys the way you would label the fields for a person, because the model reads the labels too.
Images
apex can look at PNG and JPEG images. Send them in assets, by name, and use
the name inside any string in state or in a question's instructions. An
image request must carry the header TypeSafe-Danger-Zone-Images: true. The
Python (image) tab beside this page sends a complete one.
- A name is matched anywhere inside a string, not only as a whole value, so
choose one that can't occur in your text.
asset:receiptis safe;receiptwould also match the word in"the receipt total". Keys of objects are never matched. - Each appearance is another copy of the image, and each copy is billed. An
image named in
stateis seen by every question. An image named in one question'sinstructionsis seen by that question only. - Every asset must be named somewhere. Images can't be used in
criteria. - Each image must be a single still frame, 280 to 1,568 pixels on each side and 78,400 to 2,458,624 pixels in all, at most 16 MiB.
- A request may carry at most 32 MiB of images once every copy is counted, and
an image in
statecounts once for every question. A 14 MiB image instateallows two questions; a third is refused. Base64 adds a third to the size, so the 32 MiB body limit caps the images you can send at about 24 MiB. - Send JSON with base64 data. Multipart uploads are not supported yet.
Billing
System One is billed on input tokens only, at your plan's price per million. The input is counted once per question: each question reads the whole state, so the state is billed once for every question that reads it. Ten questions about a 2,000-token state come to about 20,000 input tokens.
To keep costs down, put in the state only what the questions need, and split unrelated questions into separate requests with smaller states.
usage.input_tokens in the response is exactly the number billed. The same
number counts towards your token rate limits
(TPM and TPD), alongside the per-request RPM limit.
Limits
| Limit | Value | Over it |
|---|---|---|
| Context per question (the state plus that one question) | 32,768 tokens | 400, context_length_exceeded |
| Context per request (the state once, plus every question) | 65,536 tokens | 400, context_length_exceeded |
| Billed input per request (the state once per question, plus the questions) | 262,144 tokens | 400, context_length_exceeded |
choice options | 1 to 255 | 400 |
score levels | 1 to 10 | 400 |
Nesting in state, instructions or criteria | 64 levels | 400 |
| Request body | 32 MiB | 413 |
Errors
Errors use the standard envelope.
Requests we can check ourselves are refused before the model is called, with
param naming the field at fault.
| Status | Type | When |
|---|---|---|
| 400 | invalid_request_error | param: "model": model is missing, or names a model that isn't a System One model |
| 400 | invalid_request_error | param: "state": state is missing or null |
| 400 | invalid_request_error | param: "questions": questions is empty or isn't an object of objects, or a question's type isn't one the model answers |
| 400 | invalid_request_error | param: "assets": assets is empty or isn't an object, or the model takes no images |
| 400 | invalid_request_error | code: "context_length_exceeded": over a token limit (see above) |
| 400 | invalid_request_error | The model refused the request for another reason: missing or out-of-range criteria, an image that breaks the image rules, or images sent without the image header. code is usually invalid_request; an animated image or an image named in criteria gives unsupported_content |
| 401 | authentication_error | The API key is missing or invalid |
| 402 | insufficient_credit | Your organization is out of credit |
| 403 | permission_error | The key is inactive or expired, or the model isn't enabled in your region |
| 404 | not_found_error | No model by that name |
| 413 | invalid_request_error | The body is over 32 MiB |
| 429 | rate_limit_error | Over a rate limit, or the model isn't on your plan |
| 429 | quota_exceeded | Over a spend budget; code names which |
| 500 | api_error | Something on our side failed |
| 502 | api_error | The model failed, or answered with something we couldn't read |
| 503 | api_error | The model is out of capacity for this request right now. Retry with backoff, honouring Retry-After when it's sent |
| 504 | api_error | No answer within 90 seconds |
Two of these need care:
- A request far over a token limit is refused at once, with a message naming
the limit, before the model reads it. One nearer the limit is refused by the
model, and that message mentions
max_tokens, which System One doesn't have. Either way, shorten the state or ask fewer questions per request. - A refusal by the model doesn't say which rule failed. A
choicewith nocriteria, ascorewith 11 levels, an image that is too small andassetssent without theTypeSafe-Danger-Zone-Imagesheader all get the same sentence,"The request was rejected as invalid.". An image larger than the size limits is reported ascontext_length_exceeded. Check the request against the rules on this page.
A 503 that repeats for the same request usually means one question, with the
state, is too large to answer right now. A smaller state, or fewer images per
question, should go through.
Logging
Requests are recorded in your usage history to the depth your API key's logging
level sets: the state and questions as the request, and the answers as the
response. Image assets are never stored.
Differences from TypeSafe's API
- The state is billed once per question. TypeSafe describes its model as ingesting the state once per request. See Billing above.
boxquestions are not supported. They are refused with a400.scoreanswers have nolegend. They carryscore,confidenceandprobabilitiesonly.- No long context. The
TypeSafe-Danger-Zone-Long-Contextheader is ignored, so the 32k and 64k limits always apply. - An image's
formatmust match its bytes. A mismatch is refused, rather than read from the bytes. - JSON bodies only. Multipart assets and
gziporzstdrequest bodies are not accepted yet. GET /modelsis this platform's model list, in the OpenAI shape (data, each with anid) and priced per input token. It lists every model your plan can use, not only System One models.- Errors are ours. A validation failure is a
400, not a422, and an overloaded model is a503, not a529. The messages are our own. idis ours, and matches theX-Request-Idresponse header. Quote it when you contact support about a request.- Fields we don't know are dropped. Only
model,state,questionsandassetsare sent on to the model.
Request body
application/jsonmodelstringrequiredThe System One model, e.g. `apex`. Required: there is no default.
statestring | object | any[]requiredWhat the questions are about: text, an object or an array. Every question reads the whole state, and is billed for it.
questionsobjectrequiredThe questions, by name — at least one. Each is `{ type, instructions?, criteria? }`, where `type` is `"noul"`, `"choice"` or `"score"`; see Questions above. The answers come back under the same names.
assetsobjectImages, by name, each `{ type: "image", format, data_base64 }`. A name is used by writing it inside a string in `state` or a question's `instructions`. Requires the `TypeSafe-Danger-Zone-Images: true` header; see Images above.
Response
idstringrequiredOur identifier for the request, `so1_` followed by the `X-Request-Id` response header.
modelstringrequiredThe model that answered.
answersobjectrequiredOne answer per question, under the question's name.
usageobjectrequiredWhat the request is billed for.
Request
const response = await fetch("https://api.scx.ai/v1/systemone", {
method: "POST",
headers: {
"Authorization": "Bearer your-scx-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
"model": "apex",
"state": {
"channel": "email",
"message": "I was charged twice for my October invoice, and the refund you promised last week still has not arrived. I need this sorted today."
},
"questions": {
"urgent": {
"type": "noul",
"instructions": "Does the customer need a reply today?"
},
"topic": {
"type": "choice",
"instructions": "What is the message mainly about?",
"criteria": {
"billing": "a charge, an invoice or a refund",
"technical": "a fault or an outage",
"account": "logging in, access or account details"
}
},
"frustration": {
"type": "score",
"instructions": "How frustrated is the customer?",
"criteria": [
"calm",
"mildly annoyed",
"frustrated",
"angry"
]
}
}
}),
});
const { answers } = await response.json();
console.log(answers.urgent.noul, answers.topic.choice, answers.frustration.score);const response = await fetch("https://api.scx.ai/v1/systemone", {
method: "POST",
headers: {
"Authorization": "Bearer your-scx-api-key",
"Content-Type": "application/json",
},
body: JSON.stringify({
"model": "apex",
"state": {
"channel": "email",
"message": "I was charged twice for my October invoice, and the refund you promised last week still has not arrived. I need this sorted today."
},
"questions": {
"urgent": {
"type": "noul",
"instructions": "Does the customer need a reply today?"
},
"topic": {
"type": "choice",
"instructions": "What is the message mainly about?",
"criteria": {
"billing": "a charge, an invoice or a refund",
"technical": "a fault or an outage",
"account": "logging in, access or account details"
}
},
"frustration": {
"type": "score",
"instructions": "How frustrated is the customer?",
"criteria": [
"calm",
"mildly annoyed",
"frustrated",
"angry"
]
}
}
}),
});
const { answers } = await response.json();
console.log(answers.urgent.noul, answers.topic.choice, answers.frustration.score);Response
{
"id": "so1_7c1e5a0e-3b9f-4d0a-9f51-2f4f8f6b1c2d",
"model": "apex",
"answers": {
"urgent": {
"type": "noul",
"noul": 0.9879462122917175
},
"topic": {
"type": "choice",
"choice": "billing",
"confidence": 0.9977019429206848,
"probabilities": {
"billing": 0.9977019429206848,
"technical": 0.0013445772929117084,
"account": 0.0009534480632282794
}
},
"frustration": {
"type": "score",
"score": 1.7861143499612808,
"confidence": 0.684161365032196,
"probabilities": {
"0": 0.0650189071893692,
"1": 0.16733382642269135,
"2": 0.684161365032196,
"3": 0.0834859311580658
}
}
},
"usage": {
"input_tokens": 168,
"output_tokens": 0
}
}{
"id": "so1_7c1e5a0e-3b9f-4d0a-9f51-2f4f8f6b1c2d",
"model": "apex",
"answers": {
"urgent": {
"type": "noul",
"noul": 0.9879462122917175
},
"topic": {
"type": "choice",
"choice": "billing",
"confidence": 0.9977019429206848,
"probabilities": {
"billing": 0.9977019429206848,
"technical": 0.0013445772929117084,
"account": 0.0009534480632282794
}
},
"frustration": {
"type": "score",
"score": 1.7861143499612808,
"confidence": 0.684161365032196,
"probabilities": {
"0": 0.0650189071893692,
"1": 0.16733382642269135,
"2": 0.684161365032196,
"3": 0.0834859311580658
}
}
},
"usage": {
"input_tokens": 168,
"output_tokens": 0
}
}