Models

Choose a model with the model field of each request. Clara, the decision model, is live. Mark, the language model behind Saanora Chat, is coming soon. This page covers what each model does, the exact shape of every question and answer, what a request costs and where the model is weaker.

Available models

Every model runs on the same API keys, prepaid credit and rate limits. Prices are per 1M tokens.

ModelIDKindStatusContextInput / 1MOutput / 1M
Claraclara-1decisionLive16K tokens$0.50$0
Markmark-1chatComing soonat launchat launchat launch

List models from the API

GET /v1/models returns the models you can call right now. Use it to check an id before you ship, or to fail early if a model is not available. A model that is coming soon is not in the list until it opens.

curl https://api.saanora.com/v1/models \
  -H "Authorization: Bearer $SAANORA_API_KEY"
response
{
  "object": "list",
  "data": [
    { "id": "clara-1", "object": "model", "owned_by": "saanora", "kind": "decision", "status": "live" }
  ]
}
FieldMeaning
idThe value to send as model.
objectAlways "model".
owned_byAlways "saanora".
kindWhat the model does. "decision" for Clara.
statusAvailability, for example "live".

Clara

Clara (clara-1) is Saanora's frontier decision model. Use it wherever your software makes a decision about a record: route a support ticket, pick an agent's next tool, approve an invoice, flag a comment.

You send the record as state and the questions your process asks about it. Clara reads the record and returns a probability for every option you defined, for every question, in one response.

Call it at POST /v1/decisions. Clients written for the open System One format, such as JevBench, can call POST /v1/systemone with the same body. There, model is optional and defaults to clara-1.

A complete request

This request asks three questions about one support ticket, one of each type. Set SAANORA_API_KEY in your environment first (see Authentication).

curl https://api.saanora.com/v1/decisions \
  -H "Authorization: Bearer $SAANORA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "model": "clara-1",
    "state": "Ticket 48213 from Dana at Acme Corp (Business plan): our nightly export has failed three nights in a row. Finance needs the file by 9am tomorrow for the month-end close.",
    "questions": {
      "failing": {
        "type": "noul",
        "instructions": "Is a scheduled export currently failing?",
        "criteria": {
          "true": "The customer reports an export that does not complete",
          "false": "Exports work, or the ticket is about something else"
        },
        "threshold": 0.8
      },
      "team": {
        "type": "choice",
        "instructions": "Which team should handle this ticket?",
        "criteria": {
          "engineering": "Broken product functionality, errors and outages",
          "billing": "Invoices, charges, refunds and plan changes",
          "sales": "New purchases, upgrades and quotes"
        }
      },
      "severity": {
        "type": "score",
        "instructions": "How severe is the business impact described in the ticket?",
        "criteria": ["none", "low", "high", "critical"]
      }
    }
  }'
response
{
  "id": "dec_5b1e0c7a2f9d48e3a6c4b210",
  "object": "decision",
  "created": 1792431238,
  "model": "clara-1",
  "mode": "fast",
  "answers": {
    "failing": { "type": "noul", "noul": 0.97, "threshold": 0.8, "decision": true },
    "team": {
      "type": "choice",
      "choice": "engineering",
      "probabilities": { "engineering": 0.9, "billing": 0.06, "sales": 0.04 },
      "confidence": 0.85
    },
    "severity": {
      "type": "score",
      "score": 2.14,
      "legend": { "0": "none", "1": "low", "2": "high", "3": "critical" },
      "probabilities": { "0": 0.01, "1": 0.06, "2": 0.71, "3": 0.22 },
      "confidence": 0.71
    }
  },
  "usage": { "input_tokens": 248, "decisions": 3, "cost_usd": 0.000124 },
  "latency_ms": 96.7
}

Each answer sits under the id you gave the question (failing, team, severity). Your code reads a number and branches on it. The sections below explain every field of each answer type.

Request fields

FieldTypeRequiredDescription
modelstringYes on /v1/decisions. Optional on /v1/systemone, default clara-1.The model id. Missing: 400 missing_model. Unknown or not open yet: 404 model_not_found.
statestring or JSON objectYesThe record to decide about: a ticket, a conversation, an invoice, an agent trace. Objects are rendered compactly before the model reads them. Up to 16K tokens; longer records are shortened in the middle. Missing: 400 missing_state.
questionsobjectYesYour question ids mapped to questions. 1 to 32 questions on Tier 1, 64 from Tier 2. Missing or empty: 400 missing_questions. Too many: 422 too_many_questions.
modestringNo, default "fast""fast" or "consistent". Also accepted as the ?mode= query parameter; if both are set, the body wins. Anything else: 422 invalid_mode. See Modes.

The whole request body can be up to 1 MB; larger bodies return 413 request_too_large. Question ids are your own keys. Short, descriptive ids such as refund_eligible make the answers easy to read in code and logs.

Response fields

FieldDescription
idThe decision id, dec_....
objectAlways "decision".
createdUnix time in seconds.
model, modeThe model and mode that answered.
answersOne answer per question, keyed by your question ids. Shapes below.
usage.input_tokensBilled tokens. The same number counts toward your tokens-per-minute limit.
usage.decisionsThe number of questions answered.
usage.cost_usdExactly what was deducted from your credit.
latency_msModel time. The saanora-processing-ms header has the total time in the platform.

Every response, errors included, carries an x-request-id header; quote it when you contact support. Successful responses also carry your rate-limit headers (see Rate limits).

Question types

Every question has a type, instructions (the question itself, answerable from the record alone) and, depending on the type, criteria that define the options.

typeUse it forcriteriaMain answer field
noulYes/no decisionsOptional: {"true": "...", "false": "..."}noul: the probability of yes
choicePicking one of several optionsRequired: option id to short description, 2 to 255 optionschoice: the most likely option id
scoreA point on an ordered scaleRequired: list of levels, lowest first, at least 2score: the expected level index

Any other type returns 422 invalid_question_type. Write one decision per question, and describe each option concretely: the descriptions tell Clara what each option means. More in Best practices.

Yes/no questions (noul)

Use noul for anything with a yes or no answer: is this eligible, is this spam, is a detail missing. It is the simplest type to threshold and act on.

FieldRequiredDescription
typeYes"noul"
instructionsYesThe yes/no question.
criteriaNoAn object describing what counts as yes and as no: {"true": "...", "false": "..."}. The keys "yes" and "no" are also accepted.
thresholdNoA number from 0 to 1. When set, the answer also carries threshold and decision. Any other value is ignored.
question
"refund_eligible": {
  "type": "noul",
  "instructions": "Is this order eligible for a refund under the 30-day policy?",
  "criteria": {
    "true": "Delivered less than 30 days ago and not marked final sale",
    "false": "Delivered 30 or more days ago, or marked final sale"
  },
  "threshold": 0.9
}
answer
"refund_eligible": { "type": "noul", "noul": 0.94, "threshold": 0.9, "decision": true }

// the same question without "threshold":
"refund_eligible": { "type": "noul", "noul": 0.94 }
Answer fieldDescription
noulThe probability of yes, from 0 to 1. The probability of no is 1 - noul.
thresholdOnly when the question had one: your threshold, echoed back.
decisionOnly when the question had a threshold: true when noul >= threshold, otherwise false.

Choice questions

Use choice to pick one option from a set: a team, an intent, a tool, a policy category. The answer gives a probability for every option, so you can see when two options are close.

FieldRequiredDescription
typeYes"choice"
instructionsYesThe question.
criteriaYesAn object of option id to a short description, 2 to 255 options. Otherwise: 422 invalid_criteria.
question
"intent": {
  "type": "choice",
  "instructions": "What does the customer want?",
  "criteria": {
    "refund": "Money back for an order",
    "exchange": "A different size or item instead",
    "track": "Where an order is or when it arrives",
    "other": "Anything else"
  }
}
answer
"intent": {
  "type": "choice",
  "choice": "refund",
  "probabilities": { "refund": 0.82, "exchange": 0.11, "track": 0.04, "other": 0.03 },
  "confidence": 0.76
}
Answer fieldDescription
choiceThe most likely option id. Always one of your ids.
probabilitiesA probability for every option id. They sum to 1.
confidence(max p - 1/n) / (1 - 1/n), where n is the number of options. 1 when one option has all the probability, 0 when every option is equally likely. Here: (0.82 - 0.25) / 0.75 = 0.76.

Confidence is scaled by the number of options, so 0 always means an even spread, whether a question has 2 options or 40. Add an other or none option when “none of these” is a real outcome; without it every record is forced into one of your options.

Score questions

Use score for an ordered scale: urgency, severity, risk. List the levels from lowest to highest. Score is the least reliable type, so use it only when the order matters to you (see Limitations).

FieldRequiredDescription
typeYes"score"
instructionsYesThe question.
criteriaYesAn array of level descriptions, lowest first, at least 2. Otherwise: 422 invalid_criteria.
question
"urgency": {
  "type": "score",
  "instructions": "How urgent is this request?",
  "criteria": ["can wait a week", "this week", "today", "within the hour"]
}
answer
"urgency": {
  "type": "score",
  "score": 2.11,
  "legend": { "0": "can wait a week", "1": "this week", "2": "today", "3": "within the hour" },
  "probabilities": { "0": 0.02, "1": 0.1, "2": 0.63, "3": 0.25 },
  "confidence": 0.63
}
Answer fieldDescription
scoreThe expected level index: the sum of each index times its probability. Here: 0 × 0.02 + 1 × 0.10 + 2 × 0.63 + 3 × 0.25 = 2.11, between “today” and “within the hour”.
legendLevel index (as a string, from "0") to your level description.
probabilitiesA probability for every level index. They sum to 1.
confidenceThe probability of the most likely level. Here 0.63, for "2" (“today”).

To act on a scale, sum adjacent levels instead of reading one level alone. The probability that this request is due today or sooner is 0.63 + 0.25 = 0.88:

python
p = answers["urgency"]["probabilities"]
same_day = p["2"] + p["3"]          # P(today or within the hour) = 0.88
if same_day >= 0.8:
    page_on_call()

Modes

The mode field controls how Clara reads the record. Both modes return the same response shape.

fast (default)consistent
How it readsThe record once; every question reuses that readingThe record again with each question, so each reading takes that question into account
LatencyLowestHigher than fast
Billed tokensThe record plus each question onceThe tokens it reads: roughly the record times the number of questions
Tokens per minuteCounts the billed tokensCounts the billed tokens, so it uses your limit faster
Other questions in the requestNever change an answerNever change an answer
Options changed or a question rewordedAnswers can move moreAnswers hold steadier
JevBench-mini81.883.6
Use it forRouting and triage at volume, latency-sensitive pathsThresholds, audits, agent tool routing and other choice questions whose options change over time

In both modes each question is answered on its own, so adding or removing a question never moves the others: both modes score 100 on JevBench's batch-independence check. The gap is in the other checks. When the options of a choice question change, consistent scores 75.0 against 70.8, and when a question is reworded without changing its meaning, 91.4 against 86.9.

A worked cost example at $0.50 per 1M input tokens: a 1,000-token record with five questions of about 20 tokens each bills about 1,100 tokens in fast mode ($0.00055) and roughly 5 × 1,020 = 5,100 tokens in consistent mode ($0.00255). The exact number is always in usage.input_tokens.

Set the mode in the body, or as a query parameter: POST /v1/decisions?mode=consistent.

request
{
  "model": "clara-1",
  "mode": "consistent",
  "state": "...",
  "questions": { ... }
}
Which mode to start with. Start with fast. Switch a workflow to consistent when an answer feeds a threshold or an audit, or when you add and remove questions over time and need earlier answers to stay the same.

Validation errors

Requests that break these rules are rejected and not charged. The error body's param points at the field, for example questions.team.criteria.

StatusCodeCause
400invalid_jsonThe body is not valid JSON.
400missing_modelNo model on /v1/decisions.
400missing_stateNo state in the body.
400missing_questionsquestions is missing, empty or not an object.
400wrong_endpointThe model is not served by this endpoint.
404model_not_foundThe model id does not exist or is not open yet.
413request_too_largeThe body is over 1 MB.
422too_many_questionsMore questions than your tier allows (32 on Tier 1, 64 from Tier 2).
422invalid_question_typetype is not noul, choice or score.
422invalid_criteriaA choice without 2 to 255 options, or a score without a list of at least 2 levels.
422invalid_modemode is not fast or consistent.
400 or 422invalid_requestThe model rejected a question, for example noul criteria that are not an object. The message is the model's own.

Every status and code, including rate limits and credit, is in Errors.

Specifications

Model IDclara-1
KindDecision model
EndpointsPOST /v1/decisions; POST /v1/systemone for System One clients
Record (state)String or JSON object, up to 16K tokens. Longer records are shortened in the middle.
Questions per request32 on Tier 1, 64 from Tier 2
Question typesYes/no (noul), choice (2 to 255 options), score (2 or more ordered levels)
Modesfast (default), consistent
OutputProbabilities over your options only, never free text. 0% malformed answers.
Price$0.50 per 1M input tokens, $0 per 1M output tokens, no per-decision fee
Request bodyUp to 1 MB
SpeedAbout 0.1 s for five decisions about one record. After an idle period the first request takes about 60–80 seconds while Clara starts; consistent mode follows about 50 seconds later, and consistent requests in that window get 503 model_warming_up with a Retry-After, never charged.
LanguagesEnglish
Datastate and questions are processed in memory and never written to the database, so they are never used for training. See Data handling.

Results

BenchmarkClaraFor comparison
AgentDecisionBench, tool routing (3,939 states)93.55%Jev 1.13: 93.6%. OpenAI Decisions API: 92.0%.
JevBench-mini83.6 (consistent), 81.8 (fast)Leaderboard #1, Open-Jev-9B: 83.9
Held-out customer-service yes/no set94.9%
Malformed answers0%
SpeedAbout 0.1 s for five decisions about one record
Validate before you automate. Benchmarks describe other people's records, not yours. Before Clara acts on its own, run it on a few hundred of your own labeled records, compare its answers with the right ones, and pick a threshold for each question. How to calibrate and pick thresholds.

Limitations

Where Clara is weaker, and what to do about it.

LimitationWhat happensWorkaround
Asking for missing detailsWhen an agent should ask the user for a missing detail, Clara is right 59% of the time. It tends to pick a lookup tool instead.Add an explicit yes/no question about missing details, give it a threshold, and check it before the tool choice. Example below.
Score scalesThe least reliable question type.Use yes/no or choice where you can. When you need a scale, sum adjacent levels' probabilities instead of reading score alone.
LanguageEnglish only.Send records and questions in English.
Long recordsRecords over 16K tokens are shortened in the middle, so details there can be lost.Trim boilerplate, send only the fields the questions need, and keep the important parts near the start or end.
Your data is not the benchmarkAccuracy on your records can differ from published results.Validate on a few hundred of your own labeled records first and pick thresholds per question.

The missing-details workaround for an agent. Here state is a JSON object, and consistent mode keeps the tool choice stable as you add questions:

request
{
  "model": "clara-1",
  "mode": "consistent",
  "state": {
    "conversation": [
      { "role": "user", "content": "Can you move my booking to Friday?" }
    ],
    "tools": ["lookup_booking", "update_booking", "ask_user"]
  },
  "questions": {
    "missing_detail": {
      "type": "noul",
      "instructions": "Is a detail needed to complete the request missing from the conversation, so the agent must ask the user before acting?",
      "threshold": 0.5
    },
    "tool": {
      "type": "choice",
      "instructions": "Which tool should the agent call next?",
      "criteria": {
        "lookup_booking": "Find an existing booking by its reference",
        "update_booking": "Change the date or time of a booking",
        "ask_user": "Ask the user for missing information"
      }
    }
  }
}
act on it
a = r.json()["answers"]
if a["missing_detail"]["decision"]:   # check this first
    ask_user()
else:
    call_tool(a["tool"]["choice"])

Start the threshold at 0.5. Yes/no answers are committed (noul is at most 0.199 or at least 0.801), so any threshold from 0.2 to 0.8 behaves the same; to make the agent ask more often, lower it below 0.2 (for example 0.1), and check the result on labeled conversations where you know whether the agent should have asked.

Mark

Mark is the language model behind Saanora Chat. It is coming to the platform as mark-1, on the same keys, credit and rate limits as Clara. Its prices are published when it opens.

It is not callable yet. Until it opens, a request with "model": "mark-1" returns 404 model_not_found, is not charged, and Mark does not appear in GET /v1/models:

404 response
{
  "error": {
    "type": "not_found_error",
    "code": "model_not_found",
    "message": "model 'mark-1' does not exist or is not available",
    "param": "model"
  },
  "request_id": "req_3c9a0f5e7b2d41a8c6e9f012"
}