Guide
Decisions API tutorial: requests, answers, and the parts that are easy to get wrong
A practical Decisions API tutorial: request and response shapes, reading predicate, choice, and score answers, refusals, evidence checks, and images.
Updated - 9 min read
OpenAI’s Decisions API does one narrow job: it answers questions that have a fixed set of possible answers, and it tells you how strongly it supports the answer it gave. OpenAI announced it in late September 2026 and it is still in beta. This tutorial covers the request and response shapes, how to read each answer type, and the parts that are easy to get wrong.
Request and response shapes below are taken from the types shipped in the official SDK (openai 7.30.0 for JavaScript), which is what our own integration is built against. The API is in beta, so check OpenAI’s documentation before you depend on a detail.
What the Decisions API is
You send three things: a model, an input, and a list of questions. The input is the evidence, such as a support ticket or an email. Each question has a type, and the type determines the shape of the answer.
| Type | It asks | You get back |
|---|---|---|
predicate | Is this condition true? | A probability from 0 to 1. |
choice | Which one of these options? | The selected choice, a confidence, and a probability for every option. |
score | Where on this ordered scale? | A score, a confidence, and a probability for every level. |
There is no free-text output. The API does not write an explanation, extract fields, or call tools. If you need those, you want Structured Outputs or function calling. We compare the two approaches in Decisions API vs Structured Outputs.
Your first request
The endpoint is POST /v1/decisions. At launch, OpenAI named gpt-6-luna as the decision model; check OpenAI’s models page for what is currently available.
import OpenAI from "openai"; // openai >= 7.30.0
const client = new OpenAI(); // reads OPENAI_API_KEY; call this from your server
const decision = await 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: "shipping", description: "Delivery and tracking." },
{ value: "other", description: "Requests outside these categories." },
],
},
],
});The response contains one answer per question, in the order you asked them:
{
"answers": [
{
"type": "choice",
"name": "department",
"choice": "billing",
"probabilities": [
{ "value": "billing", "probability": 0.95 },
{ "value": "technical", "probability": 0.02 },
{ "value": "shipping", "probability": 0.01 },
{ "value": "other", "probability": 0.02 }
],
"confidence": 0.93
}
]
}Three details in that response matter. The choice is always one of the values you supplied; the model can’t invent a fifth department. The probabilities cover every option, so you can see how close the runner-up was. And confidence is 0.93 while the top probability is 0.95: they are separate numbers. The SDK types document confidence as a separate field without defining how it is computed, so don’t assume it equals the highest probability. (The numbers in this response are illustrative.)
Reading answers safely
Any question can come back as a refusal: { "type": "refusal", "name": "…" }. Check answer.type before reading any other field. A refused question is not an error at the HTTP level, and other questions in the same request are still answered.
const answer = decision.answers[0];
if (answer.type === "refusal") {
// The model declined this question. Send it to a person.
routeToHuman(ticket, "model declined");
} else if (answer.type === "choice") {
if (answer.confidence >= AUTOMATE_AT) {
assign(ticket, answer.choice);
} else {
routeToHuman(ticket, `unsure: ${answer.choice} at ${answer.confidence}`);
}
}That AUTOMATE_AT constant is the important line. The API gives you an estimate; what you do with it is your decision. Choosing that threshold is most of the work of putting a decision into production.
Ask several questions about the same input
A request can carry many questions, all evaluated against the same input. This is the feature we use most, because it solves a real problem: the API returns estimates, not reasons. If you want to know why a ticket was routed to engineering, you ask. The AI decision routing guide shows how to combine several answers into one routed action.
const decision = await client.decisions.create({
model: "gpt-6-luna",
input: ticketText,
questions: [
// The decision itself
{
type: "predicate",
name: "needs_engineering",
instructions: "Does this support issue need to go to engineering?",
},
// Evidence checks: facts you want to see and gate on
{
type: "predicate",
name: "technical_failure",
instructions: "The message reports an error, crash, or failed request.",
},
{
type: "predicate",
name: "wide_impact",
instructions: "Multiple users or production systems are affected.",
},
],
});
const byName = Object.fromEntries(decision.answers.map((a) => [a.name, a]));Give every question a unique name. Answers arrive in question order, and names keep your code correct if that list changes. The extra predicates do two jobs. A reviewer can see which facts the model found, and your code can gate on them directly: you might escalate anything with a high wide_impact probability whatever the main answer says.
Decision
Question: Which team should handle this ticket?
Route to engineering
Confidence
An estimate, not a measure of accuracy92%
Option probabilities
- 92%Route to engineering (selected)
- 6%Route to support
- 2%Route to billing
Key factors
- Technical failure detectedLikely97%
- Customer impact: highLikely88%
- Requires engineering accessLikely81%
Human review
AutomateNo
Confidence of 92% meets the automation threshold of 85%.
Images
The input can include images. Instead of a string, pass an array of user messages whose content mixes input_text and input_image parts.
const decision = await client.decisions.create({
model: "gpt-6-luna",
input: [
{
role: "user",
content: [
{ type: "input_text", text: "Customer says checkout is failing." },
{ type: "input_image", image_url: `data:image/png;base64,${base64}` },
],
},
],
questions: [
{
type: "predicate",
name: "shows_error",
instructions: "Does the screenshot show an error in the product?",
},
],
});Images must be inline base64 data URLs. External URLs and file IDs are not supported, and a request can contain at most 128 images. Only user messages are accepted: no system role, no tool calls, no files or audio.
What it does not do
- It doesn’t explain itself. You get numbers. Evidence checks are the supported way to see more.
- It doesn’t know your policy. “Should this refund be approved?” is answered against the model’s general sense of reasonableness unless your question or input states the rule.
- It doesn’t promise accuracy. A probability of 0.9 is the model’s estimate. Whether answers at that level are right nine times in ten is something you measure, with labeled examples from your own application.
- It can change. This is a beta API. Keep provider calls behind one function in your codebase so a change touches one file.
Cost and latency
At launch, OpenAI described the Decisions API as returning answers about ten times faster than the Responses API, and priced input at $0.10 per million tokens with no charge for output tokens. Both are OpenAI’s figures as reported at launch, not ours, and prices change: confirm them on OpenAI’s pricing page. We have not yet published our own latency measurements. When we do, they will be on the benchmarks page with the method attached.
Where to go next
- Predicate vs choice vs score: picking the right question type.
- Decision templates: worked decisions for support and operations, ready to adapt.
- Calibration: turning labeled examples into thresholds.
Related guides
- Guide
Predicate vs choice vs score
Most decisions can be phrased three ways, and each returns different information. Which type fits which decision, with worked examples and common mistakes.
- Guide
Confidence thresholds
Probability, confidence, accuracy, calibration, and correctness are different things. How to set a review policy from the cost of each kind of mistake.
- Guide
AI decision routing
A routing decision is several smaller decisions plus a rule that combines them. How to design that rule, where people fit, and what to log.
Templates that use these ideas
- Predicate
Engineering escalation
Whether a support issue needs engineering, or can be resolved by support.
- Choice
Customer message routing
Which team should own a customer message: sales, customer success, support, or billing.
- Score
Ticket urgency
How urgently a support ticket needs a response, on a four-level rubric.
Put it into practice
Try a decision, check it against labeled examples, and talk to us if you want help building one for your workflow.