cCarebun Reading‹ All reading
Carebun Interactive/Decision models Decision labCheat sheet Study view

Part IV · Ninja

Chapter 17

OpenAI's Decisions API

A second vendor now sells the same three stamps under different field names. We read its published guide, map it to Jev field by field, and write our code so the engine can be swapped. We could not call it, and we say so.

12 min read · interactive

On this page
  1. What it is, and what we know
  2. Three question types, the same three stamps
  3. Rosa's question on both forms
  4. What each one has that the other lacks
  5. Write the desk so the engine can be swapped
  6. Is a language model that says a number the same thing?
  7. Try it yourself
  8. Common questions
  9. Carry this
  10. Check yourself

In this chapter, we will learn to read and write the other decision API: the one that OpenAI's changelog lists as released in beta on 6 October 2026. We will map it to Jev field by field, price Rosa's call under both, and write a thin layer of code that lets the desk change engines. One thing first: we have read this API, and we have not run it.

What it is, and what we know

OpenAI's Decisions API is an endpoint that takes text or images plus a list of typed questions, and returns typed answers with probabilities instead of generated text. An endpoint is one address on a server that accepts one kind of request.

OpenAI's guide says the API "evaluates text, images, or both and returns typed answers about 10x faster than the Responses API", its ordinary endpoint for generated text. The guide gives no figure in milliseconds, so we have no latency to compare with our own.

FactWhat OpenAI's guide says
Status"in public beta, and we expect to GA in the coming weeks". GA means general availability.
Modelgpt-6-luna "is the only model currently available"
EndpointPOST https://api.openai.com/v1/decisions
SDK method (an SDK is the vendor's own code library for calling its API)client.decisions.create(...)

Three question types, the same three stamps

OpenAI's three question types are predicate, choice and score, and they match the yes/no dial, the tray stamp and the ruler. A predicate is a statement that is either true or false, such as "the product has visible damage". It is the Noul of Chapter 4 under an older name from logic. Its answer has one field of substance, probability, which is 0.92 in the guide's example of a damaged product. There is no yes or no in it. Our code draws the line.

The guide's own request for a choice:

{
  "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."}
    ]
  }]
}

And this is the answer the guide prints for it: an illustration by OpenAI, not a program's output.

{
  "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 differences show at once. The evidence is called input, where Jev calls it state. The questions are an array, each with a name, where Jev takes a map keyed by your own ids. The answers are an array too, so our code finds an answer by its name. Independent questions of different types may share one array, as in Chapter 9.

The guide's score example rates "Export fails in Safari but works in Chrome." against three levels. Each level is an object with a short label and a description. The illustrative answer gives the levels the probabilities 0.1, 0.7 and 0.2, and a score of 1.1. The guide says the score is "the probability-weighted average of the level indices", and that indices start at 0. That is the rule of Chapter 5.

score    0 x 0.1  +  1 x 0.7  +  2 x 0.2   =  0 + 0.7 + 0.4   =  1.1

Rosa's question on both forms

To translate a question, keep its words and move them into the other vendor's boxes. The desk's severity question from Chapter 5 has one instruction and four described levels in a criteria array. Here is the same question built on the pattern of the guide's score example, with the two middle descriptions cut short here. We wrote it. We did not run it.

{
  "type": "score",
  "name": "severity",
  "instructions": "How severe is the problem described?",
  "levels": [
    {"label": "Cosmetic", "description": "Cosmetic. Nothing stops working."},
    {"label": "Inconvenient", "description": "..."},
    {"label": "Essential service lost", "description": "..."},
    {"label": "Danger", "description": "Danger to people or to the building: fire, gas, flooding, electric shock."}
  ]
}

Translation forced one decision on us. Every level in the guide's example carries a label, and Rosa's desk never had one, so we invented four. Whether a label changes the answers, only a test can tell.

Before you open the interactive, predict: when the may_enter question moves from Jev's form to OpenAI's, how many field names change? Then pick it and count.

We count three renamed fields: state becomes input, the type noul becomes predicate, and the answer field noul becomes probability. One field appears, name. One disappears, criteria, and we folded its two sentences into the instructions.

WhatJev (TypeSafe's documentation)OpenAI Decisions (OpenAI's guide)
The evidencestate: a string, an object or an array; text onlyinput: a string, or user messages with text and image parts
The questionsa map from your own id to a questionan array; each question has a name
Yes/notype noul, answer field noultype predicate, answer field probability
Help for yes and for nooptional criteria with true and falsenone shown; instructions only
Options of a choicecriteria: a map from option to descriptionchoices: an array of value and description
Levels of a scorecriteria: an ordered array of descriptionslevels: an ordered array of label and description
Probabilitiesa map; a score adds a legendan array of objects; a score's objects carry value and label
Confidenceon choice and score; formulas publishedon choice and score; no formula in the guide

What each one has that the other lacks

The two APIs agree on the three stamps and differ at the edges: images and refusals on one side, published formulas and limits on the other.

Images. OpenAI's guide says "Images must be inline base64 data URLs": the picture itself, written out as a long string of letters inside the request. Jev's documentation says its input is text only. For Rosa this matters, because tenants send photos of ceilings.

Refusals. The code samples in OpenAI's guide check whether an answer has type: "refusal". The guide does not say when that happens. Jev's documentation describes no refusal type. Code for OpenAI's API therefore needs one more branch: what the desk does when the clerk declines to stamp.

A confidence formula. TypeSafe publishes its formulas, and experiment 0014 reproduced them. OpenAI's guide returns a confidence and defines none. We can at least try TypeSafe's formulas on OpenAI's two illustrative answers.

choice   4 options, top probability 0.95
         (0.95 - 1/4) / (1 - 1/4)   =  0.70 / 0.75   =  0.933     the guide prints 0.93

score    3 levels, most likely level 1; probabilities 0.1, 0.7, 0.2
         spread       0.1 x 1  +  0.2 x 1             =  0.3
         even spread for 3 levels                     =  2/3
         1 - 0.3 / (2/3)   =  1 - 0.45                =  0.55      the guide prints 0.55

Both match. That is two invented examples, not a definition, and may be a coincidence. If a threshold depends on confidence, we compute it ourselves from the probabilities.

Limits. TypeSafe documents up to 255 options and 2 to 10 levels. OpenAI's guide states no maximum for choices, levels or questions.

Price. Both charge for input tokens only. A token is a small piece of text, as in Chapter 7. Rosa's call with three questions measured a mean of 694 input tokens on Jev in experiment 0002.

Jev       694 x $0.042 / 1,000,000   =  $0.0000291     $0.029 per 1,000 requests   (measured tokens, published price)
OpenAI    694 x $0.10  / 1,000,000   =  $0.0000694     $0.069 per 1,000 requests   (IF it counted the same 694 tokens)
ratio     0.10 / 0.042               =  2.4

The second line rests on an assumption: another model cuts text into tokens differently, and we could not measure it. OpenAI's guide adds that "Regional processing premiums and long-context input pricing multipliers apply".

Write the desk so the engine can be swapped

An adapter is a small piece of code that translates between our own format and a vendor's format. The desk keeps its questions in a shape of its own: a kind, the words to ask, and the options or levels. One function per vendor copies them onto that vendor's form, and one reads the answers back into a common shape.

# A sketch. It follows the published guide. We have never sent its output to OpenAI.
def to_openai(text, questions):
    qs = []
    for name, q in questions.items():
        if q["kind"] == "yes_no":
            ask = q["ask"] + " True when: " + q["yes"] + " False when: " + q["no"]
            qs.append({"type": "predicate", "name": name, "instructions": ask})
        elif q["kind"] == "choice":
            qs.append({"type": "choice", "name": name, "instructions": q["ask"],
                       "choices": [{"value": v, "description": d} for v, d in q["options"].items()]})
        else:
            qs.append({"type": "score", "name": name, "instructions": q["ask"],
                       "levels": [{"label": l, "description": d} for l, d in q["levels"]]})
    return {"model": "gpt-6-luna", "input": text, "questions": qs}

Its partner from_openai walks the answers array and returns {"p_yes": ...}, {"pick": ...}, {"score": ...} or {"refused": True} under each name. A Jev pair, to_jev and from_jev, is shorter, because Jev's form already takes a map of questions. All four functions are sketches: the experiments in this book send the questions of experiments/desk.py to Jev directly. The rest of the desk, the lookup table, the gate and the templates of Chapter 11, sees only p_yes, pick, score and refused.

Is a language model that says a number the same thing?

No: a language model that writes "0.8" has generated text that looks like a probability, and a decisions endpoint returns the number as a typed field. We can still ask how good the written number is. Experiment 0018 put one statement, "The situation described is a danger to people or to the building.", to 281 requests. Two language models were told to reply with a probability in JSON.

requests: 281   dangerous: 59
...
way                             Brier  right at 0.5  distinct values  in 0.1-0.9  p50 ms  $ / 1,000
jev                             0.080         88.3%               79       53.7%     235      0.019
...
gpt-5.1, says a number          0.088         88.6%               53       59.1%     996      0.445
gpt-5.6-luna, says a number     0.122         83.6%               38       22.4%    1428      0.098

The Brier score is the average squared gap between the number given and what was true (1 or 0). Zero is perfect, always saying 0.5 scores 0.25, and lower is better. Chapter 18 works it by hand.

By this table Jev is ahead, 0.080 against 0.088. But the truth behind it is the label "severity level 3", written for another question. Experiment 0038 scored the same answers again, against the majority of three language models that read the statement itself (Chapter 18 explains both truths).

way                             severity label  readers majority
jev                                      0.080             0.073
...
gpt-5.1, says a number                   0.088             0.063
gpt-5.6-luna, says a number              0.122             0.066
caution: gpt-5.1 was one of the three readers, so its two rows are judged partly by itself.

The honest reading: gpt-5.1's written number was close to Jev's by both truths, and the order changes with the truth. Jev is first by one and last by the other. The data is synthetic and gpt-5.1 wrote it. We name no winner on quality. The gap in time and money does not depend on a truth.

p50 time 996 / 235       =  4.2 times slower
cost     0.445 / 0.019   =  23 times dearer

gpt-5.6-luna, which its listing calls "a fast, cost-efficient model", had the slowest p50 here, 1,428 ms. It is not the gpt-6-luna behind OpenAI's Decisions API, and says nothing about it.

Try it yourself

On paper, translate the desk's trade question into OpenAI's form: six options become six objects with value and description. Check yourself against the interactive.

If you have a key for OpenAI's platform, do what we could not: send the guide's choice request, and where a field differs from this chapter, trust the live reply.

Common questions

Which of the two is more accurate?

We do not know. We measured Jev on 215 requests and could not call the other. The test is cheap once you have a key: the same requests, the same questions, the same scoring.

Does OpenAI's model see the question's name?

The guide does not say. TypeSafe's documentation says that question ids are not sent to the model. On both, write the whole question in instructions.

Is the confidence the same number on both?

For Jev the formula is published. For OpenAI it is not, although the guide's two examples agree with TypeSafe's formulas. Compute your own from the probabilities if a threshold depends on it.

What should my code do with a refusal?

Treat it like an unsure answer and send the request to a person. Count refusals in your logs: the guide does not say how often to expect them.

Carry this

  • OpenAI's Decisions API: POST /v1/decisions, model gpt-6-luna, public beta. Types predicate, choice, score.
  • Same stamps, other boxes: input for state, arrays with name for maps, probability for noul, choices and levels for criteria.
  • OpenAI adds images and a refusal answer. TypeSafe publishes its confidence formulas and its limits.
  • Published prices: $0.10 against $0.042 per million input tokens, output free on both.
  • We did not run OpenAI's API. Keep questions in your own shape, and set thresholds again after any swap.

Check yourself

1. A desk sends 50,000 requests a month, each of 694 input tokens. What does a month cost at each published price, if both engines counted the same tokens?

Answer
tokens    50,000 x 694                     =  34,700,000
Jev       34.7 million x $0.042 / million  =  $1.46
OpenAI    34.7 million x $0.10  / million  =  $3.47

At this size, accuracy on your own data should decide, not price.

2. An OpenAI score answer has three levels with probabilities 0.5, 0.3 and 0.2. What is the score?

Answer
score    0 x 0.5  +  1 x 0.3  +  2 x 0.2   =  0 + 0.3 + 0.4   =  0.7

3. Name three things your code must change when it moves a yes/no question from Jev to OpenAI's API.

Answer

The type is predicate and not noul. The answer is read from probability and not from noul, and it is found by name in an array. And the code needs a branch for type: "refusal".