Decisions
POST /v1/decisions answers typed questions (choice, score, yes/no) about one input, with probabilities per question. Shapes, limits, billing, errors.
Answer typed questions about a piece of input. You send a state (the text or
JSON to decide about) and a set of questions; each answer comes back as a
label or a score with its probabilities and a confidence. The model answers
each question with a single token, and only input tokens are billed.
POST https://inference.openrelay.inc/v1/decisionsThe model that serves this family is openrelay/qwen3.6-35b-decide, built on
Qwen3.6-35B-A3B by the Qwen team
at Alibaba (Apache 2.0): a mixture-of-experts model with 35B parameters, 3B of
them active per token. Check a model's supported_apis on
GET /v1/models for decisions; a model that does not
list it answers 400 unsupported_api. Decisions are online only: the
Batch API does not accept /v1/decisions.
Question types
Every question has one of three types:
type | criteria | The answer |
|---|---|---|
choice | The options: an object of label: description, or an array of labels. | choice, the most likely label, and probabilities over every label. |
score | An array of levels, ordered from lowest to highest. | score, the expected level index from 0 to n - 1 (fractional), a legend from index to level, and probabilities per index. |
noul | Optional. An object with false and/or true descriptions. | noul, the probability that the answer is yes. |
Request
| Field | Type | Description |
|---|---|---|
model | string (required) | openrelay/qwen3.6-35b-decide. |
state | string, object, or array (required) | The input to decide about. Objects and arrays are serialized to JSON text (with a space after each , and :) before the model reads them. |
questions | object (required) | Question id to question. Between 1 and 64 questions. |
Each question:
| Field | Type | Description |
|---|---|---|
type | string (required) | choice, score, or noul. |
instructions | string, object, or array (required) | What to decide. An object or array is JSON-encoded. |
criteria | object or array | The options, per the table above. Required for choice and score. |
Any other field is rejected with 400 unknown_field, at the top level, inside a
question, or inside noul criteria. That includes checkpoint, task, and
lang. stream is dropped: a decision is always a single JSON response.
Use question ids made of ASCII letters, digits, _, and -. They come back as
the keys of answers, and an error names them in its param field (see
Errors).
Object key order is not preserved
The gateway re-encodes the request body, which sorts the keys of every JSON
object alphabetically. That applies to an object state and to choice
criteria given as an object. If the order of your input matters, send state
as a string. answers are keyed by question id, so their order
carries no meaning.
How a question is answered
Each question is one prompt to the model: the state first, then the
instructions, then the options lettered A, B, C, and so on, after a
short fixed system prompt. The model produces one token, restricted to those
letters, and the answer comes from the probabilities of the letters,
renormalized over the options. A noul question is asked as A. yes /
B. no, with the criteria.true and criteria.false descriptions attached to
their sides when you send them, and noul is the probability of yes.
The whole state is read for every question. Nothing is truncated.
Response
{
"model": "openrelay/qwen3.6-35b-decide",
"answers": {
"department": {
"type": "choice",
"choice": "billing",
"probabilities": { "billing": 1.0, "technical": 0.0 },
"confidence": 0.9995
},
"urgency": {
"type": "score",
"score": 1.5995,
"legend": { "0": "not urgent", "1": "soon", "2": "blocking" },
"probabilities": { "0": 0.0314, "1": 0.3377, "2": 0.6309 },
"confidence": 0.3028
},
"churn_risk": {
"type": "noul",
"noul": 0.9999,
"confidence": 0.9999
}
},
"usage": { "prompt_tokens": 539, "total_tokens": 539, "truncated_tokens": 0 },
"openrelay": { "cost_nano_usd": 26950, "priced_at": "online" }
}That is the response to the request in Examples.
| Field | Description |
|---|---|
model | The public model id you requested. |
answers | One entry per question, keyed by the ids you sent. |
answers.<id>.type | The question's type. |
answers.<id>.choice | choice only: the label with the highest probability. |
answers.<id>.score | score only: the probability-weighted level index, from 0 to n - 1. |
answers.<id>.legend | score only: level index to the level you sent. |
answers.<id>.noul | noul only: the probability that the answer is yes. |
answers.<id>.probabilities | choice and score: the distribution over labels or level indices. |
answers.<id>.confidence | choice and score: one minus the normalized entropy of probabilities (0 is a uniform guess, 1 is certain). noul: the larger of noul and 1 - noul. |
usage.prompt_tokens | The tokens the model read, summed over questions, which is what you are billed. |
usage.total_tokens | Equal to prompt_tokens. There is no completion_tokens. |
usage.truncated_tokens | Always 0: every question reads the whole state. |
openrelay.cost_nano_usd | What the request is billed, in nanoUSD, also sent as the OpenRelay-Cost-Nano-USD header. See Cost on every response. |
Limits
| Limit | Value | Over the limit |
|---|---|---|
| Questions per request | 64 | 400 too_many_questions (zero is 400 empty_questions) |
Options per choice or score question | 26, one letter each, on every input | 400 too_many_options |
state size | 32 KB (32,768 bytes of serialized UTF-8) | 400 state_too_large |
| Request body | 10 MiB | 413 payload_too_large |
Billing
openrelay/qwen3.6-35b-decide costs $0.050 per 1M input tokens. It has no
output rate.
The state is part of every question's prompt, so usage is the sum of each
question's prompt tokens: the state, the instructions, the options, and a
short fixed system prompt. A request with ten questions reads the state ten
times, and bills for it. Asking several questions in one request saves round
trips, not billed tokens.
usage.prompt_tokens in the response is the number you are billed on, and
openrelay.cost_nano_usd what you are billed; they are the source of truth, not
any estimate you compute from the request size. The response above bills 539
tokens, 539 x 50 = 26,950 nanoUSD. A request that fails with a non-2xx status is
not billed. See Pricing & billing.
Errors
A request the model rejects gets a typed error with a documented code and, in
most cases, a param naming the field:
{
"error": {
"message": "too many options for one question",
"type": "invalid_request_error",
"code": "too_many_options",
"param": "questions.department.criteria"
}
}The codes this family can return, all 400 with type invalid_request_error:
code | message | param |
|---|---|---|
empty_questions | the request has no questions | questions |
too_many_questions | too many questions in one request | questions |
too_many_options | too many options for one question | questions.<id>.criteria |
invalid_question_type | unsupported question type | questions.<id>.type |
state_too_large | the request state is too large | state |
unknown_field | the request carries a field this API does not define | The field's path, e.g. checkpoint or questions.<id>.critera |
invalid_request | the request is not valid for this API | The field's path, when there is one. Covers every other shape error: a state that is not a string, object, or array, missing instructions, empty, non-string, or duplicate choice labels, or a score whose criteria is not a list. |
param is a dotted path into your own request, with [n] for array indices.
It is present only when that path exists in what you sent, so a missing field
(for example a question with no instructions) comes back without a param.
It is also omitted when the path is longer than 128 bytes or passes through a
question id that contains a dot, a space, or any character other than ASCII
letters, digits, _, and -.
Every other error is the gateway's, listed on Errors:
401 for the key, 402 for the balance, 404 for an unknown model,
400 unsupported_api for a model that does not serve decisions, 413 for the
body size, 429 when the model's queue is full, and 502 or 503 while the
model is restarting or has no ready capacity. Retry 429, 502, and 503 with
backoff (after the Retry-After header on a 429); do not retry a 400
unchanged.
What to expect
Our own zero-shot measurement, one question per request, no examples in the prompt, on public labeled sets (2026-10-22):
| Set | Question | Accuracy | Mean confidence | Calibration error (ECE) |
|---|---|---|---|---|
| SST-2, 872 movie reviews | choice: negative or positive | 95.8% | 0.975 | 0.023 |
| BoolQ, 500 passages | noul: the answer to a yes/no question | 92.4% | 0.970 | 0.053 |
| XNLI English, 300 pairs | choice: entailment, neutral or contradiction | 88.7% | 0.936 | 0.058 |
| XNLI German / Arabic / Chinese / Hindi, 300 pairs each | the same | 73.7% to 79.3% | 0.870 to 0.907 | 0.124 to 0.168 |
| XNLI Swahili, 300 pairs | the same | 66.3% | 0.866 | 0.210 |
What that means for you:
- Accuracy on your task is the number that matters. Measure it on your own labeled data before you rely on the answers.
confidencetracks accuracy closely on English text and runs ahead of it in other languages, most of all in lower-resource ones. Pick any confidence threshold from your own labeled data, per language if your traffic mixes them.- The model reads the whole
statefor every question, in any of the 201 languages Qwen lists. The limits and the price do not change with the language.
Examples
curl https://inference.openrelay.inc/v1/decisions \
-H "Authorization: Bearer $OPENRELAY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "openrelay/qwen3.6-35b-decide",
"state": {
"subject": "Duplicate charge on invoice #4411",
"body": "I was charged twice. If this is not fixed today I will cancel."
},
"questions": {
"department": {
"type": "choice",
"instructions": "Which department handles this?",
"criteria": { "billing": "invoices, refunds", "technical": "bugs, outages" }
},
"urgency": {
"type": "score",
"instructions": "How urgent?",
"criteria": ["not urgent", "soon", "blocking"]
},
"churn_risk": {
"type": "noul",
"instructions": "Does the user threaten to leave?"
}
}
}'