Skip to content
OpenRelay is in early access, and the /v1 API is stable. New capabilities ship in the changelog.
Skip to Content
GuidesInference APIs

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/decisions

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

typecriteriaThe answer
choiceThe options: an object of label: description, or an array of labels.choice, the most likely label, and probabilities over every label.
scoreAn 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.
noulOptional. An object with false and/or true descriptions.noul, the probability that the answer is yes.

Request

FieldTypeDescription
modelstring (required)openrelay/qwen3.6-35b-decide.
statestring, 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.
questionsobject (required)Question id to question. Between 1 and 64 questions.

Each question:

FieldTypeDescription
typestring (required)choice, score, or noul.
instructionsstring, object, or array (required)What to decide. An object or array is JSON-encoded.
criteriaobject or arrayThe 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.

FieldDescription
modelThe public model id you requested.
answersOne entry per question, keyed by the ids you sent.
answers.<id>.typeThe question's type.
answers.<id>.choicechoice only: the label with the highest probability.
answers.<id>.scorescore only: the probability-weighted level index, from 0 to n - 1.
answers.<id>.legendscore only: level index to the level you sent.
answers.<id>.noulnoul only: the probability that the answer is yes.
answers.<id>.probabilitieschoice and score: the distribution over labels or level indices.
answers.<id>.confidencechoice 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_tokensThe tokens the model read, summed over questions, which is what you are billed.
usage.total_tokensEqual to prompt_tokens. There is no completion_tokens.
usage.truncated_tokensAlways 0: every question reads the whole state.
openrelay.cost_nano_usdWhat the request is billed, in nanoUSD, also sent as the OpenRelay-Cost-Nano-USD header. See Cost on every response.

Limits

LimitValueOver the limit
Questions per request64400 too_many_questions (zero is 400 empty_questions)
Options per choice or score question26, one letter each, on every input400 too_many_options
state size32 KB (32,768 bytes of serialized UTF-8)400 state_too_large
Request body10 MiB413 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:

codemessageparam
empty_questionsthe request has no questionsquestions
too_many_questionstoo many questions in one requestquestions
too_many_optionstoo many options for one questionquestions.<id>.criteria
invalid_question_typeunsupported question typequestions.<id>.type
state_too_largethe request state is too largestate
unknown_fieldthe request carries a field this API does not defineThe field's path, e.g. checkpoint or questions.<id>.critera
invalid_requestthe request is not valid for this APIThe 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):

SetQuestionAccuracyMean confidenceCalibration error (ECE)
SST-2, 872 movie reviewschoice: negative or positive95.8%0.9750.023
BoolQ, 500 passagesnoul: the answer to a yes/no question92.4%0.9700.053
XNLI English, 300 pairschoice: entailment, neutral or contradiction88.7%0.9360.058
XNLI German / Arabic / Chinese / Hindi, 300 pairs eachthe same73.7% to 79.3%0.870 to 0.9070.124 to 0.168
XNLI Swahili, 300 pairsthe same66.3%0.8660.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.
  • confidence tracks 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 state for 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?"
      }
    }
  }'

On this page