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.

POST/v1/systemone

Authorization

Authorizationstringheaderrequired

Bearer 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

ModelQuestion typesImagesContext per request
apexnoul, choice, scoreyes65,536 tokens

Questions

Each entry in questions is { type, instructions?, criteria? }. The name you give it is the key its answer comes back under.

typecriteriaAnswer
noulOptional { "true": "…", "false": "…" } describing the two outcomes (default "yes" and "no")noul: the probability of yes
choiceRequired: 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
scoreRequired: an array of 1 to 10 level descriptions, lowest firstscore: 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: value line per key, with a list of strings under a key written one - item per line;
  • an array as one - item line 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:receipt is safe; receipt would 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 state is seen by every question. An image named in one question's instructions is 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 state counts once for every question. A 14 MiB image in state allows 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

LimitValueOver it
Context per question (the state plus that one question)32,768 tokens400, context_length_exceeded
Context per request (the state once, plus every question)65,536 tokens400, context_length_exceeded
Billed input per request (the state once per question, plus the questions)262,144 tokens400, context_length_exceeded
choice options1 to 255400
score levels1 to 10400
Nesting in state, instructions or criteria64 levels400
Request body32 MiB413

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.

StatusTypeWhen
400invalid_request_errorparam: "model": model is missing, or names a model that isn't a System One model
400invalid_request_errorparam: "state": state is missing or null
400invalid_request_errorparam: "questions": questions is empty or isn't an object of objects, or a question's type isn't one the model answers
400invalid_request_errorparam: "assets": assets is empty or isn't an object, or the model takes no images
400invalid_request_errorcode: "context_length_exceeded": over a token limit (see above)
400invalid_request_errorThe 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
401authentication_errorThe API key is missing or invalid
402insufficient_creditYour organization is out of credit
403permission_errorThe key is inactive or expired, or the model isn't enabled in your region
404not_found_errorNo model by that name
413invalid_request_errorThe body is over 32 MiB
429rate_limit_errorOver a rate limit, or the model isn't on your plan
429quota_exceededOver a spend budget; code names which
500api_errorSomething on our side failed
502api_errorThe model failed, or answered with something we couldn't read
503api_errorThe model is out of capacity for this request right now. Retry with backoff, honouring Retry-After when it's sent
504api_errorNo 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 choice with no criteria, a score with 11 levels, an image that is too small and assets sent without the TypeSafe-Danger-Zone-Images header all get the same sentence, "The request was rejected as invalid.". An image larger than the size limits is reported as context_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.
  • box questions are not supported. They are refused with a 400.
  • score answers have no legend. They carry score, confidence and probabilities only.
  • No long context. The TypeSafe-Danger-Zone-Long-Context header is ignored, so the 32k and 64k limits always apply.
  • An image's format must match its bytes. A mismatch is refused, rather than read from the bytes.
  • JSON bodies only. Multipart assets and gzip or zstd request bodies are not accepted yet.
  • GET /models is this platform's model list, in the OpenAI shape (data, each with an id) 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 a 422, and an overloaded model is a 503, not a 529. The messages are our own.
  • id is ours, and matches the X-Request-Id response header. Quote it when you contact support about a request.
  • Fields we don't know are dropped. Only model, state, questions and assets are sent on to the model.

Request body

application/json
modelstringrequired

The System One model, e.g. `apex`. Required: there is no default.

statestring | object | any[]required

What the questions are about: text, an object or an array. Every question reads the whole state, and is billed for it.

questionsobjectrequired

The 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.

assetsobject

Images, 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

idstringrequired

Our identifier for the request, `so1_` followed by the `X-Request-Id` response header.

modelstringrequired

The model that answered.

answersobjectrequired

One answer per question, under the question's name.

usageobjectrequired

What 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);

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
  }
}