If your app asks a large language model a yes-or-no question, you have probably lived through this: you request a clean label, the model replies with a paragraph, and somewhere in your codebase a regular expression tries to drag the word "refund" out of "I believe this does constitute a refund request." It works on Monday. By Friday the model is feeling chatty, the regex misses, and a support ticket goes to the wrong queue.
On 6 October, OpenAI put a public beta of a different answer in front of developers: the Decisions API, a new endpoint — POST /v1/decisions — that returns typed verdicts instead of prose. No paragraphs to parse, no JSON mode to coax, just a probability, a choice, or a score. This is a practical guide: what the endpoint does, what it costs, where it genuinely helps, and the calibration work it quietly leaves to you.
What the Decisions API actually is
The idea is deliberately narrow. You send evidence (input) plus one or more questions, and the API answers each question in a fixed shape. Three question types exist:
Predicate — a yes-or-no condition, answered with a probability between 0 and 1. "Does this photo show visible product damage?"
Choice — a fixed set of candidate answers you define, answered with the winning choice, the option probabilities and a confidence value. "Which department owns this ticket?"
Score — an ordered rubric you define, answered with a probability-weighted average across the levels, plus a confidence. "How severe is this incident?"
A refusal is a separate answer type — not a low score and not your fallback category. As of mid-October the API is in public beta, with gpt-6-luna as the only supported model, and OpenAI says it expects general availability "in the coming weeks". That is an expectation, not a date.
Your first decision in five minutes
The quickest way to feel the shape of the API is a support-routing call. With curl:
curl https://api.openai.com/v1/decisions \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"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."}
]
}]
}'
In Python the call mirrors OpenAI's published guide (the SDK surface is still beta and may change):
from openai import OpenAI
client = OpenAI()
decision = client.decisions.create(
model="gpt-6-luna",
input="I was charged twice for my order #48210.",
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."},
],
}
],
)
answer = decision.answers[0]
print(answer.name, "->", answer.choice, f"(confidence {answer.confidence:.2f})")
The response carries each answer under the name you gave its question, so a single request can ask several independent questions about the same input — say, a queue and a severity — and get back a tidy list instead of a paragraph to dissect.
Handle refusals before you read the value
The part most tutorials skip is the one that breaks production: the model can refuse a question. A refusal arrives as its own answer type, and your code must branch on it before touching probability, choice or score. A small routing helper makes the policy explicit:
REVIEW = "manual_review"
def route(answer, cutoffs):
if answer.type == "refusal":
return REVIEW
if answer.type != "choice":
return REVIEW
cutoff = cutoffs.get(answer.choice)
if cutoff is None or answer.confidence is None or answer.confidence other choice and a manual-review queue; a confident wrong answer costs more than an honest "I don't know". Second, set the cutoffs from your own labelled data — more on that below.
## The pricing: input-only billing
In public beta the API costs **$0.10 per million input tokens**, with no output, cache-read or cache-write charges. Regional processing premiums and long-context multipliers can apply on top, so treat the headline number as a floor.
Put some numbers on a realistic workload. A moderation pipeline making 50,000 decisions a day at 500 input tokens each burns 25 million tokens daily — about **$2.50 a day, or roughly $75 a month**. At current rates that is around €0.09 per million input tokens, which makes the arithmetic easy for EU teams budgeting in euros. These are calculations from published prices, not measured invoices: your instructions, choice descriptions and retries all add tokens.
How does that compare with doing the same job through the Responses API? The saving is the output tokens you no longer pay for — on the same model those are billed at $0.50 per million. On short verdict-style answers the difference is modest, so the honest reason to switch is rarely the token bill. It is the engineering around it: less waiting in serial workflows, less response-parsing code, and fewer 2 a.m. pages because the model decided to answer in haiku.
## What is genuinely new — and what isn't
Three things are actually new here. The typed contract is first-class: your code consumes an answer type instead of a string, which deletes a whole class of parsing bugs. The billing matches the contract: you stop paying for output tokens on decisions that were never prose. And the response carries confidence and explicit refusals, which is the information a routing policy needs and the information plain JSON mode never gave you.
What is not new is the underlying trick. You could already ask a model for JSON with a label and a confidence number. OpenAI's headline claim — decisions up to 10× faster than GPT-6 Luna via the Responses API — is OpenAI's claim, with no independent measurement published as of this writing. Treat it as a benchmark to reproduce on your own workload, not a fact about yours.
The deepest non-newness is this: the API does not remove the hard part. It removes the parsing. Deciding what a 0.72 probability *means* for your business is still your job.
## Calibrating thresholds: the work OpenAI leaves to you
OpenAI publishes no calibration numbers and tells developers to set thresholds from their own labelled examples, weighing the cost of false positives against false negatives. A confidence value is not a promise that the answer will be right that often on your data.
A workable calibration loop looks like this:
- Collect a few hundred past cases per question and have a domain expert label the correct answer.
- Sweep the cutoff and plot the trade-off: wrong-queue assignments against the share of cases sent for human review.
- Pick the cutoff where the marginal review cost stops buying meaningful accuracy.
- Keep a held-out set you never tune on, and re-measure there before promoting anything.
Then run the candidate in shadow mode — scoring live traffic without acting on it — and promote it only when it beats your current approach on a written acceptance rule. Keep the old route available for rollback. Nothing about this is specific to OpenAI's API; it is the same discipline a serious team applies to any classifier, and skipping it is how "AI routing" projects quietly die.
## Beta limits worth knowing
- **One model.** gpt-6-luna is the only option in beta; there is no way to trade accuracy for cost yet.
- **Images are inline base64 only** in the current guide — fine for a damaged-parcel photo, awkward for bulk pipelines.
- **Dependent questions need separate requests.** Ask independent questions together, but a follow-up whose meaning depends on an earlier answer belongs in a later call.
- **The surface may move.** Beta SDKs and response shapes can change before general availability; pin your SDK version and validate responses defensively.
## When to use it, and when to skip it
Good fits share a pattern: stable categories, observable mistakes, and a human who already owns the exceptions. Support-ticket routing, content-moderation queues (under the EU's Digital Services Act, note that automated decisions still need human oversight for serious actions), data labelling, retrieval filtering for RAG pipelines, and decision gates inside agent loops all qualify. The agent-loop case is worth a second look: a cheap typed check before an expensive action is exactly where a 10× latency claim, if it holds on your workload, would pay for itself.
Skip it when the answer needs to be prose anyway, when a trained scikit-learn classifier already hits your accuracy bar at a fraction of the latency, or when the stakes demand a human and no confidence threshold makes you comfortable. "Our model is 94% confident" is not a compliance strategy.
## The bottom line
The Decisions API is the first OpenAI endpoint designed around the way production code actually consumes model output: as a value in a fixed set, with a confidence attached and a refusal path. At $0.10 per million input tokens with no output billing, it is cheap enough to try on any classification workload you already run. The catch is the part OpenAI is honest about — thresholds, calibration and review policy are yours to build. Teams that do that work get a genuinely simpler system. Teams that skip it just get wrong answers faster.
*Code samples follow OpenAI's published Decisions guide and community SDK samples; they were not executed against a live account. Pricing is from OpenAI's published beta rates; the 10× speed figure is OpenAI's claim, not an independent measurement.*
Top comments (1)
have you tried using the function calling feature to get a strict boolean instead of regex‑parsing the model's free‑form reply?