A Practical Guide to OpenAI's New Decisions API

Is this the JEV killer?

Written by
Thomas Reid
Published by
Towards Data Science
Published
Length
2,254 words · 10 min
A Practical Guide to OpenAI's New Decisions API

Is this the JEV killer?

Prefer us on GoogleShare

I recently covered TypesafeAI’s JEV in TDS (link at the end). JEV makes fast decisions, such as classifying content or choosing the next step in a workflow, and returns typed answers that software can use directly. The accompanying probabilities help an application decide whether to act on an answer or pass it to a person for review.

The internet went a bit crazy over JEV, so it was no surprise to see rival products appear after its release. Among the most prominent was the Decisions API that OpenAI previewed at its recent DevDay and made available as a public beta shortly after.

In this article, I’ll look at the new API, how to get it, and some practical use-case examples, and I’ll let you know whether I think it’s a JEV killer or whether TypesafeAI can sleep soundly at night.

The background to JEV-like models

Suppose you run an online shop. A customer writes to say that their keyboard arrived with three broken keys and asks for a replacement. Before anyone replies, your application needs to decide which team should handle the message.

You could ask a language model to classify it and return JSON. But the application only needs a category and enough information to decide whether to trust that category. That process could also be relatively slow and costly if it had to handle 1000s of requests.

OpenAI’s Decisions API gives that operation its own endpoint. Additionally, OpenAI claims its Decisions API can make decisions up to ten times faster than GPT-6 Luna, the LLM that Decisions is based on, through the Responses API. To explore how it works, we’ll walk through three Python examples and compare OpenAI’s approach with TypesafeAI’s JEV model.

One major advantage of the Decisions API over JEV is that it can handle images. We’ll see an example of that later.

What the OpenAI Decisions API returns

A request supplies a model, some input and a list of questions. The current beta supports the gpt-6-luna LLM. Questions share the input, which can contain text, images or both. 

There are three question types:

plaintext
+-----------+--------------------------------------------------+------------------------------+| Type      | Question                                         | Result                       |+-----------+--------------------------------------------------+------------------------------+| predicate | Does the document contain a return deadline?     | A probability between zero   ||           |                                                  | and one                      |+-----------+--------------------------------------------------+------------------------------+| choice    | Which support queue should receive this message? | A selected value,            |       |           |                                                  | probabilities and confidence |+-----------+--------------------------------------------------+------------------------------+| score     | How severely does this incident affect customers?| A score across ordered       ||           |                                                  | levels, probabilities and    |          |           |                                                  | confidence                   |+-----------+--------------------------------------------------+------------------------------+

These fields are defined in the SDK’s response types. A predicate doesn’t return a Boolean: your code chooses the threshold that turns its probability into an action.

The API returns answers in question order. It can also refuse individual questions, so check an answer’s type before reading its other fields. 

Defining the available answers is part of designing the application. Suppose your choices are only billing and delivery, but the customer asks about opening hours. Neither response is appropriate, but adding a general category gives the system a sensible place to route that request. Test the categories against real customer messages, including requests that don’t fit any of them.

With the Decisions API, you choose from three question types: predicate, choice and score. Each returns a defined answer format, including probabilities. If you need to extract an invoice number, a customer name and a list of purchased items into your own JSON structure, use Structured Outputs with the Responses API.

When to use the Decisions API and what it costs

A support system processing thousands of messages may only need a queue name before passing each request to the right team. The Responses API can produce that answer, but the Decisions API provides a dedicated interface for questions with defined answers: a yes/no probability, a choice from a list or a score against a rubric.

You send those questions to /v1/decisions and use the returned answers in your application logic. That makes it a useful option for routing messages, selecting a search index or choosing an agent’s next action from a permitted list.

It also has separate pricing. At launch, OpenAI lists $0.10 per million input tokens, with no charges for output tokens, cache reads, or cache writes. Long-context multipliers and regional processing premiums still apply. 

As a simple calculation, one million requests averaging 1,000 billable input tokens would cost $100 at that base rate. Include the questions and their descriptions when estimating input size.

A task that needs a written explanation or an extracted object with arbitrary fields still needs a generation interface. It doesn’t make sense to squeeze an invoice extraction problem into twenty classification questions just because the endpoint is fast.

Equally, keep simple rules in Python. If priority depends only on an order total exceeding £500, for example, compare the number directly in Python code itself. A model becomes useful when the input expresses something your rules can’t easily recognise, such as a customer describing the same fault in several different ways. Even then, the extra network call must save enough downstream work to justify its latency.

Set up Python with the API

The official openai-python repository added Decisions support in version 3.26.0. Install that version in your environment:

powershell
c:\> python -m pip install openai==3.26.0

You’ll need an OpenAI API key. If you don’t already have one, you need to make sure you have registered with OpenAI and added a payment method with some credit to your account. Afterwards, go to https://platform.openai.com/home. On the left side of the screen, you’ll see an API keys link. Click on that and follow the instructions to create a new secret key.

Set the OPENAI_API_KEY environment variable to your API key. Do that in PowerShell like this:

powershell
c:\> $env:OPENAI_API_KEY="your-api-key"

Each example below is a complete, standalone Python program with its own imports and client setup.

The SDK implementation exposes client.decisions.create() and sends the request to the /v1/decisions endpoint. This is the client implementation; inference runs on OpenAI’s service.

Save each program using the filename shown in its section, then run it directly with Python. The three files work independently. Each program makes a billable API request when you run it.

Code example 1: Routing a support request

Our shop has three specialist queues and a general queue for anything that doesn’t fit. Create a file named decisions_route.py and add this code.

python
from openai import OpenAIMODEL = "gpt-6-luna"def main() -> None:    with OpenAI(timeout=20.0) as client:        result = client.decisions.create(            model=MODEL,            input="My keyboard arrived with three broken keys. Can you replace it?",            questions=[{                "type": "choice",                "name": "queue",                "instructions": "Choose the queue for the customer's main request.",                "choices": [                    {"value": "returns", "description": "Damaged goods or replacements."},                    {"value": "billing", "description": "Charges or invoice errors."},                    {"value": "delivery", "description": "Missing or delayed deliveries."},                    {"value": "general", "description": "Everything else or unclear intent."},                ],            }],        )        answer = result.answers[0]        if answer.type == "refusal":            print("Send to manual triage")        elif answer.type == "choice":            print(answer.choice, answer.confidence)            queue = answer.choice if answer.confidence >= 0.8 else "manual_triage"            print("Queue:", queue)if __name__ == "__main__":    main()

The (correct) output to my question about a broken keyboard was:

powershell
c:\> python decisions_route.pyreturns 1.0Queue: returns

When I asked a different question in the code,

plaintext
Do you have a website I could look at?

I got this output, which is correct again.

powershell
c:\> python decisions_route.pygeneral 0.8Queue: general

The choice objects contain values and descriptions, as specified in the SDK’s request types. Descriptions let us distinguish damaged goods from a delivery that never arrived.

The 0.8 threshold is illustrative. It isn’t an OpenAI recommendation, and it doesn’t establish an 80% success rate. Start with messages people have already classified, and measure mistakes at several thresholds before you decide on your threshold.

Code Example 2: Check a document for missing information

Suppose staff write return instructions in several formats. We want to flag instructions that omit either a deadline or a postal address. Create the file decisions_document.py with this content.

python
from openai import OpenAI, RateLimitErrorMODEL = "gpt-6-luna"def main() -> None:    with OpenAI(timeout=20.0) as client:        checks = {            "deadline": "Does the text explicitly give a deadline for returning an item?",            "address": "Does the text explicitly provide a postal return address?",        }        result = client.decisions.create(            model=MODEL,            input="Return the item within 30 days. Email support to request our address.",            questions=[                {"type": "predicate", "name": name, "instructions": question}                for name, question in checks.items()            ],        )        for answer in result.answers:            if answer.type == "refusal":                print(answer.name, "review required")            elif answer.type == "predicate":                status = "present" if answer.probability >= 0.9 else "check manually"                print(answer.name, status, answer.probability)if __name__ == "__main__":    try:        main()    except RateLimitError as exc:        if exc.code == "credit_balance_exhausted":            raise SystemExit(                "Your OpenAI API credit balance is exhausted. Add credits for "                "the organisation associated with your API key at "                "https://platform.openai.com/settings/organization/billing/ "                "and then run this example again."            ) from None        raise

My output was:

powershell
c:\> python decisions_document.pydeadline present 1.0address check manually 0.0

The wording is important. Being told to request an address isn’t the same as being given one. A search for the word address would miss that distinction.

Both questions can share one request because neither depends on the other’s answer. If a later question needs an earlier result, make another request after inspecting that result.

This checks whether information appears in the text. Checking whether an address exists or whether a deadline matches your business rules needs separate validation.

Code Example 3: Inspect a product photograph

Our last example uses three images of a parcel, one heavily damaged, one with slight damage and the other completely undamaged. We’ll see if the model can distinguish between damaged/undamaged. Here are the images I used. All were in .PNG format.

Place all three images in the same location as the Python scripts, called, say, undamaged.png, heavy_damage.png and slight_damage.png. Next, create a file called decisions_image.py with this code.

python
import base64from pathlib import Pathfrom openai import OpenAI, RateLimitErrorMODEL = "gpt-6-luna"PARCEL_IMAGES = ("undamaged.png", "heavy_damage.png", "slight_damage.png")def main() -> None:    with OpenAI(timeout=20.0) as client:        for filename in PARCEL_IMAGES:            image_path = Path(__file__).with_name(filename)            encoded = base64.b64encode(                image_path.read_bytes()            ).decode("ascii")            result = client.decisions.create(                model=MODEL,                input=[{                    "role": "user",                    "content": [                        {                            "type": "input_text",                            "text": "Image of a delivered parcel.",                        },                        {                            "type": "input_image",                            "image_url": f"data:image/png;base64,{encoded}",                        },                    ],                }],                questions=[{                    "type": "predicate",                    "name": "visible_damage",                    "instructions": (                        "Does the packaging visibly have a tear, "                        "hole or crushed corner?"                    ),                }],            )            answer = result.answers[0]            if answer.type == "refusal":                print(f"{filename}: Inspect the photograph manually")            elif answer.type == "predicate":                print(                    f"{filename}: Probability of visible packaging "                    f"damage: {answer.probability}"                )if __name__ == "__main__":    try:        main()    except RateLimitError as exc:        if exc.code == "credit_balance_exhausted":            raise SystemExit(                "Your OpenAI API credit balance is exhausted. Add credits for "                "the organisation associated with your API key at "                "https://platform.openai.com/settings/organization/billing/ "                "and then run this example again."            ) from None        raise

Images must use inline data URLs; ordinary web URLs and file IDs aren’t accepted. The endpoint supports up to 128 images per request, and its message input supports only the user role with text and image parts.

Here are my outputs:

powershell
c:\> python decisions_image.pyundamaged.png: Probability of visible packaging damage: 0.05heavy_damage.png: Probability of visible packaging damage: 1.0slight_damage.png: Probability of visible packaging damage: 0.98

That last result surprised me. The package was only slightly damaged, but the model identified it with high probability. That’s pretty impressive, though I accept that even slight damage can be visible and may lead to a high score.

Summary: How Decisions compares with JEV

JEV addresses a similar programming problem to the Decisions API. Its interface evaluates shared state using three primitives: Noul, Choice, and Score. Noul returns the probability of a yes/no answer, making it the closest equivalent to OpenAI’s predicate, which we used in examples 2 and 3. JEV’s choice primitive is largely the same as OpenAI’s, which we used in our first example.

The products behind those interfaces differ. OpenAI exposes GPT-6 Luna through a specialised endpoint. TypeSafeAI describes JEV as a model built for decisions, with a parallel sampler and Reinforcement Learning for Calibrated Decisions, or RLCD.

The practical comparison, as of early October 2026, looks like this:

plaintext
+-----------------------------+------------------+-------------------------------------------+| Feature                     | OpenAI Decisions | Jev 1.13                                  |+-----------------------------+------------------+-------------------------------------------+| Model                       | gpt-6-luna       | jev-1.13.0                                |+-----------------------------+------------------+-------------------------------------------+| Input                       | Text and images  | Text, including structured textual state  |+-----------------------------+------------------+-------------------------------------------+| Questions                   | Ordered array    | Map keyed by question name                |+-----------------------------+------------------+-------------------------------------------+| Yes/no                      | predicate ->     | noul question -> noul                     ||                             | probability      |                                           |+-----------------------------+------------------+-------------------------------------------+| Choose an option            | choice -> choice | choice -> choice, probabilities,          ||                             | probabilities,   | confidence                                ||                             | confidence       |                                           |+-----------------------------+------------------+-------------------------------------------+| Score against a rubric      | score -> score,  | score -> score, probabilities,            ||                             | probabilities,   | confidence, legend                        ||                             | confidence       |                                           |+-----------------------------+------------------+-------------------------------------------+|Price per million tokens     | $0.10            | $0.042                                    |+-----------------------------+------------------+-------------------------------------------+| Output-token charge         | None             | None                                      |+-----------------------------+------------------+-------------------------------------------+

JEV’s listed input token cost is 58% lower, although different tokenisation and request sizes affect the bill. Its current context limits are 64,000 tokens for the complete request and 32,000 for the state plus the longest question. 

TypeSafeAI reports 70–500 millisecond response times in its launch post. During my testing of the OpenAI Decisions API, response times often ran into multiple seconds, but I didn’t time a direct comparison between the two products.

For both systems, Python still controls what happens next after the models return their results. Keep arithmetic and fixed business rules in ordinary code, and use a generative model when the task is more ambiguous and needs a written explanation.

So, finally, do I think JEV should be worried? No, not yet, at least. In my experience, JEV has the advantage of speed; Decisions API has the advantage of interpreting images. But how long do you think it will be before JEV can process images? 

You can read my original TDS article on JEV here.

Undamaged parcel
Undamaged parcel Towards Data Science
Heavily damaged parcel
Heavily damaged parcel Towards Data Science
Slightly damaged parcel
Slightly damaged parcel Towards Data Science

Where this came from

This story was reported by Thomas Reid and first published by Towards Data Science on 11 October 2026. HUE Legacy Ventures did not write it.

Carried in full with attribution and a link to the original. Rights remain with the publisher, who may request removal at any time.

Read it at towardsdatascience.com →