If you route support tickets, flag content or gate agent actions, you've probably asked a model for a label and parsed the text. The OpenAI Decisions API skips the text. It returns probabilities for each question you ask, so you can set thresholds instead of trusting a single word. It reached general availability on 22 October 2026 with one model, gpt-6-luna.
I read the Decisions guide, the changelog and the GPT-6 Luna model page on 30 October 2026. I had no API key, so I did not call the live endpoint. The router below runs for real, but against a mock whose answers I wrote by hand in the documented shape.
What is the Decisions API?
POST /v1/decisions evaluates text, images and JSON and returns typed answers. The guide says it runs "about 10x faster than the Responses API" and gives no benchmark behind that figure, so treat it as a vendor claim. Changelog dates: beta on 6 October, native integration in GPT-Live on 15 October, GA on 22 October.
A request has three parts: model, input (shared evidence, as a string or ordered messages, tool calls and tool outputs) and questions. Every question has a unique name, instructions and a type.
| Type | Use it for | Returns |
|---|---|---|
predicate | Yes/no conditions | probability, 0 to 1 |
choice | One of your unordered labels | choice, probabilities, confidence |
score | Ordered levels, 2 to 10 | score (probability-weighted index), probabilities, confidence |
Limits from the page: 1 to 400 questions per request, user messages can carry text, images and JSON, and file_id inputs, audio, files and item references are not supported. The page documents no error codes, so write your handler defensively.
What does a request look like?
This is the documented choice example, in Python. I copied the shape from the guide and did not run it.
from openai import OpenAI
client = OpenAI()
decision = client.decisions.create(
model="gpt-6-luna",
input="I was charged twice for my order.",
questions=[
{
"type": "choice",
"name": "department",
"instructions": "Which department should handle this complaint?",
"choices": [
{"value": "billing", "description": "Payments, invoices, and refunds."},
{"value": "technical", "description": "Problems using the product."},
{"value": "other", "description": "Requests outside these categories."},
],
}
],
)
answer = decision.answers[0]
print(answer.choice, answer.confidence)
Score questions take levels with a label and description instead of choices. Note the guide's distinction: use choice for unordered categories and score for ordered ones, because a score can land between levels (a 1.1 means mostly "workaround", a bit toward "blocked").
How do I build a triage router on top of it?
Ask three questions in one call: is it urgent enough for a human right now (predicate), which department (choice) and how urgent (score). Then route in plain code. The thresholds are yours to tune against labelled tickets, which is the real value of getting probabilities back.
The script is below. The live call is defined but never executed. mock_call returns answers I wrote by hand, labelled illustrative, so the output proves the routing logic and nothing about model accuracy.
from types import SimpleNamespace as NS
QUESTIONS = [
{"type": "predicate", "name": "needs_human_now",
"instructions": "Does the customer report money lost, a security problem, or a legal threat?"},
{"type": "choice", "name": "department",
"instructions": "Which department should handle this?",
"choices": [
{"value": "billing", "description": "Payments, invoices, refunds."},
{"value": "technical", "description": "Problems using the product."},
{"value": "other", "description": "Anything else."}]},
{"type": "score", "name": "urgency",
"instructions": "How urgent is this?",
"levels": [
{"label": "Low", "description": "No deadline."},
{"label": "Normal", "description": "Wants an answer this week."},
{"label": "High", "description": "Blocked or losing money now."}]},
]
def route(decision, human_p=0.5, min_conf=0.7):
by = {a.name: a for a in decision.answers}
if any(a.type == "refusal" for a in decision.answers):
return "human_queue (refusal)"
if by["needs_human_now"].probability >= human_p:
return "human_queue (flagged)"
dept = by["department"]
if dept.confidence < min_conf or by["urgency"].confidence < min_conf:
return "human_queue (low confidence)"
s = by["urgency"].score
prio = "high" if s >= 1.5 else "normal" if s >= 0.5 else "low"
return f"{dept.choice} / {prio}"
Running it over four mock cases (a double charge, a vague message, a refusal and a plain export bug) printed this on my machine:
double -> human_queue (flagged)
vague -> human_queue (low confidence)
refused -> human_queue (refusal)
export bug -> technical / low
Three design points worth copying. First, every failure path ends in a human queue, including a refusal. Second, the confidence check is separate from the predicate check, because a model can be sure a ticket is billing and unsure about urgency. Third, the thresholds (0.5, 0.7, 1.5) are placeholders I picked. Replace them using a golden dataset built from your own logs, and look at where wrong answers cluster before trusting any number.
Decisions API vs structured outputs: which should I use?
They solve different problems.
| Need | Decisions API | Structured outputs |
|---|---|---|
| A threshold you can tune | Yes, probabilities returned | Only a label unless you build it |
| Many labels from one input | Up to 400 questions in one request | One schema, one generation |
| Extracting fields such as dates or names | No | Yes |
| An explanation you can read | No | Yes, if you add a field |
| Cost shape | Input tokens only | Input plus output tokens |
Do not read the returned probability as a guarantee of correctness. The guide describes the values but does not claim they are calibrated against real outcomes. Before you use 0.9 as a cut, measure how often answers above 0.9 are actually right on your data.
If you need explanations, a hybrid works: use Decisions to route and escalate cheaply, and call a normal model only for the cases that land in the middle. This fits the layered setup in the moderation layer post, where a cheap classifier sits in front of slower checks.
What does it cost?
From the guide on 30 October: uncached input is $0.10 per 1M tokens, cache hits are discounted, cache writes are charged, and output tokens are not charged. Regional processing premiums and long-context multipliers apply. The GPT-6 Luna model page lists cached input at $0.01 and cache writes at $0.125 per 1M tokens, plus 2x on input for prompts over 272K tokens. It also notes regional processing adds a 10% premium where available.
A worked example, my arithmetic and not a measurement: 1,000,000 tickets at 500 input tokens each is 500M tokens, which is 500 x $0.10 = $50 at the uncached rate. Real cost depends on whether your question text counts as input tokens. The page doesn't say, so assume it does and test with a small batch.
Shared input is cached by default once the reusable prefix reaches 1,024 tokens, so put your long rubric or policy text in input and keep per-question instructions short. prompt_cache_key partitions cache reuse. The page says breakpoints are not supported inside question instructions.
What should I watch for?
- One model only.
gpt-6-lunais the only supported model, so you can't trade up for accuracy on hard cases inside this endpoint. - No images by file ID. Use base64 data URLs or public URLs.
- No documented errors. Retry on network failures, and send the item to a human on anything you don't recognise.
- Request counts, not question counts. The usage endpoint counts
num_model_requestsper request, so cost dashboards won't show how many questions you asked. - Adversarial inputs. A ticket can contain text that tries to steer the classifier. Keep Decisions as one signal, and don't let a single low-urgency answer auto-close anything. See prompt injection defense in production.
What is unverified
I did not call the endpoint, so I can't report latency, accuracy or whether probabilities are calibrated. The "10x faster" claim is OpenAI's. The Luna model page does not list /v1/decisions among its supported endpoints, which only list Chat Completions, Responses and Batch; the guide and changelog are the source for Decisions support.



