Skip to content
TheThinkLoop

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.

TypeIt asksYou get back
predicateIs this condition true?A probability from 0 to 1.
choiceWhich one of these options?The selected choice, a confidence, and a probability for every option.
scoreWhere 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.

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

Responsejson
{
  "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.

Handling an answerjavascript
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.

A decision with evidence checksjavascript
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

Illustrative example

Question: Which team should handle this ticket?

Route to engineering

Confidence

An estimate, not a measure of accuracy

92%

Option probabilities

  • Route to engineering (selected)
    92%
  • Route to support
    6%
  • Route to billing
    2%

Key factors

  • Technical failure detectedLikely97%
  • Customer impact: highLikely88%
  • Requires engineering accessLikely81%

Human review

Automate

No

Confidence of 92% meets the automation threshold of 85%.

Hand-written to show the format. Not provider output.
A Decision Card for a routing choice with three evidence checks.

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.

Text and an imagejavascript
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

Templates that use these ideas

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.