Most support queues run on small judgments. Is this a refund request. Which team owns it. How bad is it. Teams often pay a chat model to answer each judgment in prose, then pay again to parse the prose back into a value the code can use. Google shipped a narrower tool for that job on November 11, 2026. The Gemini Decisions API, powered by the model Gemini Decision 1, generates no text at all. It scores the options you give it and returns numbers.
This tutorial builds a batch checker on that API. One request carries one support ticket and four questions about it. The answers come back as probabilities with confidence scores, and a short routing block sends sure cases onward and holds unsure cases for a person. The steps follow the published documentation and the code examples in the official guide. No live run with a real key is claimed here. The Verification section tells you how to prove the checker works in your own account.
What You Will Build or Learn
You will end with a small Python script that sends one ticket to the Decisions API and gets four judgments back in one call. The script asks if the ticket asks for a refund, which department should handle it, how severe the problem is, and whether a person should review it before anyone acts. It then prints each answer with its confidence and routes the ticket by rules you control.
You will also learn the three question types and the limits that shape any batch design. A yes or no question is type noul in the API. A pick from a fixed set is type choice. A grade on an ordered scale is type score. Each question is judged on its own against the shared input. Adding a question does not change the answers to the others, and the shared input can mix text with images, audio, video and PDF files.
One input in, many judgments out. The machine sorts. Your code decides what each pile means. Illustration: Dirty Static
Before You Begin
Decide which judgments in your queue are truly bounded. The API fits questions with a fixed set of answers, such as routing, policy checks, tool selection and grading against a rubric. It cannot explain a decision, pull a value out of a document, or reason through a chain of steps. If a ticket needs a written reply or a chain of thought, that part still belongs to a chat model or a person.
Price shapes the design, so note it before you build. Google charges $0.10 per million input tokens on the paid tier, and output is never billed because the model produces zero output tokens. The shared input is billed once per request no matter how many questions ride on it. There is no context caching and no Batch API support, so a request you repeat bills its input in full each time. Google reports full request times of about 150 to 250 milliseconds, nearly flat from 1 to 100 questions on inputs up to 16K tokens. Those are Google figures. Measure them on your own tickets before you design around them.
Step 1: Get a Key and Install the SDK
Action: open Google AI Studio, create an API key, and store it in an environment variable with a placeholder name in your notes. Then install the SDK at version 3.2 or later. Older versions do not carry the decisions namespace at all.
export GEMINI_API_KEY="YOUR_API_KEY"
pip install -U google-genai
Expected result: the variable is set for your shell session and the install reports google-genai at version 3.2 or later. You can also try questions with no code in the Decisions Playground in AI Studio, which exports runnable code when you are ready.
Step 2: Ask One Yes or No Question
Action: start with the smallest call the guide gives. The client must name the v1beta API version, or the decisions namespace does not appear. This example uses the damaged mug text from the official guide.
from google import genai
client = genai.Client(http_options={"api_version": "v1beta"})
result = client.decisions.create(
model="gemini-decision-1",
input="The blue mug I ordered on Monday arrived cracked on Wednesday.",
questions={
"is_damaged": {
"type": "noul",
"instructions": "Did the item arrive damaged?",
},
},
)
answer = result.answers["is_damaged"]
print(f"P(yes)={answer.noul:.2f} confidence={answer.confidence:.2f}")
Expected result: the call returns an answers map under the same key you sent, plus a usage block where output tokens read zero. The guide shows a noul value of 0.99 with a confidence of 0.99 for this text. The noul field is the probability that the answer is yes, and it always falls between 0.01 and 0.99. Confidence for this type is the larger of the value and one minus the value, so a value of 0.03 carries a confidence of 0.97 that the answer is no. The same call over REST goes to POST https://generativelanguage.googleapis.com/v1beta/decisions with your key in the x-goog-api-key header. Note the path. This model does not answer on the generateContent path that other Gemini models use.
Step 3: Turn One Call Into a Batch Check
Action: keep the same client and grow the questions map. This batch runs four checks on one ticket. It mixes all three types, and it adds a scope check that asks if the message concerns an order at all. That scope check matters in Step 5.
ticket = "I was charged twice for my order and the export page crashes in Safari."
result = client.decisions.create(
model="gemini-decision-1",
input=ticket,
questions={
"about_order": {
"type": "noul",
"instructions": "Is this message about an order, a payment or a delivery?",
},
"is_refund": {
"type": "noul",
"instructions": "Does the customer ask for a refund or a charge reversal?",
},
"department": {
"type": "choice",
"instructions": "Which department should handle this request?",
"criteria": {
"billing": "Payments, invoices, and refunds.",
"technical": "Problems using the product.",
"shipping": "Delivery and tracking.",
"other": "Anything outside these categories.",
},
},
"severity": {
"type": "score",
"instructions": "How severe is this issue for the user?",
"criteria": [
"Cosmetic: appearance only, no lost functionality.",
"Workaround available: a task fails, but another way works.",
"Fully blocked: a task fails with no workaround.",
],
},
},
)
Expected result: one response holds four answers, one per key. The choice answer names the top option and gives a probability for every option. The score answer gives a weighted average across the levels, so it can land between levels, and it carries a probability for each level plus a legend that maps level numbers to your level text. Three facts from the docs shape how you write these questions. The model never sees your question keys, so the full question must live in the instructions text. Keys must run from 1 to 64 characters drawn from letters, digits, underscores and hyphens. The API sorts choice options by key before it scores them, and a tie on exact probability goes to the option whose key sorts first. Name your safety net option other, as in this example. A name such as none_of_the_above sorts ahead of real options and can win ties it should lose.
Step 4: Add a Photo or a PDF to the Same Input
Action: when a check needs more than the ticket text, send the input as a list of blocks instead of a string. Blocks can be text, image, audio, video or document, and document means PDF only. Set the true media type on each block, because the API rejects application/octet-stream. Inline data is capped at 100 MB per request, and at 50 MB for PDF files. Files at external URLs are capped at 15 MB each, and YouTube links and gs:// addresses are not supported. Use the Files API, a public or signed HTTPS address, or inline bytes instead.
import base64
with open("photo.jpg", "rb") as f:
image_b64 = base64.b64encode(f.read()).decode("utf-8")
result = client.decisions.create(
model="gemini-decision-1",
input=[
{"type": "text", "text": "Customer photo for return 8821.\n"},
{"type": "image", "data": image_b64, "mime_type": "image/jpeg", "resolution": "low"},
],
questions={
"visible_damage": {
"type": "noul",
"instructions": "Does the photo show physical damage to the item? Ignore the packaging.",
},
},
)
Expected result: the photo joins the text as one shared input, and every question in the same request can judge both. The resolution setting trades visual detail for tokens, with values low, medium, high and ultra_high, and the last of those serves images only. A batch can grow to 1,000 questions per request, inside a total limit of 262,144 tokens shared by the input and the questions.
Step 5: Route on Probability and Confidence
Action: read the value to pick an action and read the confidence to decide if the action may run on its own. The numbers below are starting points from the guide examples. Replace them with cutoffs you earn in Step 6.
a = result.answers
dept = a["department"]
if a["about_order"].noul < 0.5:
route_to("general_intake")
elif dept.confidence >= 0.7 and dept.choice != "other":
route_to(dept.choice)
elif dept.confidence >= 0.4:
route_to_fallback_model(ticket)
else:
route_to("human_review")
Expected result: sure cases move on, middling cases get a second look from a larger model that can reason, and weak cases wait for a person. Two rules from the docs protect this block. A choice question always splits its probability across the options you gave it, even when none of them fits, which is why the scope check runs first in this code. A score value is an average, not a level. When you need one level, take the level with the highest probability and read its text from the legend, as this short block does.
sev = result.answers["severity"]
level = max(sev.probabilities, key=sev.probabilities.get)
print(sev.legend[level])
Step 6: Test With Your Own Labeled Tickets
Action: build a small set of past tickets where a person has already recorded the right department, the right severity and the refund call. Run the batch on each one and compare the top answers with the labels. Count how often each confidence band proves right, then set your routing cutoffs from those counts. Google advises exactly this. Thresholds should come from labeled examples of your own traffic, and the same set tells you if the probabilities mean what they claim on your data.
Expected result: you hold a measured cutoff for each route, plus a list of tickets the batch gets wrong that you can study. Keep the set and rerun it after any edit to instructions or criteria. A safety block needs care in this step too. When the input filter blocks a request, the API returns HTTP 400 with an error code such as safety or prohibited_content and no answers at all. Record that outcome as no decision and send the ticket to a person. A blocked ticket is often the one that most needs human eyes.
The pile that waits for a person is a feature of the design, not a failure of it. Illustration: Dirty Static
Verification
Run these checks and record the results. The script sends one request for a single ticket and the usage block reports zero output tokens. The answers map holds every key you sent, and each answer carries a confidence between 0 and 1. The mug text from Step 2 returns a high noul value, and a ticket with no order content routes to general intake through the scope check. Your labeled set from Step 6 passes at the rate your cutoffs assume, and a blocked input lands in human review rather than in a queue.
Finish with the cost check. Price one request by hand from its input token count at $0.10 per million tokens, then compare that figure with the cost of the same checks sent as separate chat calls that resend the ticket each time. The gap between those two figures is the whole reason this endpoint exists.
Troubleshooting
| Problem | Likely cause | Fix |
|---|---|---|
| The decisions namespace does not appear on the client | The SDK sits below version 3.2, or the client lacks the v1beta setting | Install google-genai at 3.2 or later and create the client with the v1beta API version, as in Step 2 |
| A REST call returns 404 or a model error | The call went to the generateContent path or to version v1 | Send the request to POST /v1beta/decisions with the model gemini-decision-1, as in Step 2 |
| A threshold rule never fires | The cutoff sits above 0.99 or below 0.01, and noul values never leave that range | Keep yes or no cutoffs inside 0.01 to 0.99 and set them from your labeled set |
| The department answer picks an option when none fits | A choice question must spread probability across the options it was given | Run a scope check of type noul first, as in Steps 3 and 5, and route failures away before you trust the choice |
| A score prints as a fraction and looks wrong | The score field is a weighted average across levels, not a level index | Read the level with the highest probability and print its legend text, as in Step 5 |
| The whole request returns HTTP 400 with no answers | A safety filter blocked the input, and the error code names the block | Treat the result as no decision and send the item to human review. Never read a block as a no on every question |
| Costs rise on repeat runs of the same input | The API offers no context caching, so each request bills the shared input in full | Batch more questions into each request and cut repeat calls on inputs that have not changed |
Next Steps
Grow the batch where the shape fits. A contract or a screenshot can face dozens of policy rules in one request. A router can ask which tool fits a task and which model tier should take it, the same control flow pattern our guide to sandboxed agent sessions guards from the other side. Keep each new question independent, add it to the labeled set first, and let its answers earn trust at low stakes before they move money or close tickets.
The habit worth keeping is the split this API forces. Judgment calls that end in a fixed set of answers go to the batch. Work that needs words, reasons or a chain of steps goes elsewhere. Queues get faster when each kind of work runs on the tool that was built for it.
Facts checked against the Gemini Decisions API documentation and guide examples on November 12, 2026. Prices, limits and latency figures are Google published values and can change, so confirm them in the docs before a wider rollout. This procedure was verified against those documents and is not presented as a personal test run.
Sources
- Google, Gemini Decisions API documentation and guide, checked November 12, 2026: https://aistudio.google.com/docs/decisions-api
- Google, Gemini Decisions API announcement, November 11, 2026, via Google Blog
- Apidog, How to Use the Gemini Decisions API, November 2026: https://apidog.com/blog/how-to-use-gemini-decisions-api/
- TeamoRouter, Gemini Decision API: Price, the Three Question Types and How to Call It, November 12, 2026: https://teamorouter.com/blogs/gemini-decision-api
- ImportStatic, Gemini Decisions API in Go: a REST client and its traps, November 12, 2026: https://importstatic.com/ai/gemini-decisions-api-go
- Hero, sorter and review images: Dirty Static illustrations
END OF TRANSMISSION
Stay curious. Question everything.


