DocsFeatures
Decision models
Run decision policies, rerank, classifier guardrails, the route classifier and evals on the decision model of your choice, on your own account with its provider.
INFRO is in private early access. Running these examples requires an early-access account, an active trial or subscription entitlement, and your own provider connection. Self-serve purchasing is not generally open. See Early access.
A decision model answers typed questions — the probability that a statement is true, one option of a closed set, a level on a rubric — and generates no text. TypeSafe's Jev was the first; OpenAI, Cloudflare, Perplexity, Inception, Liquid AI, Upstage and Microsoft now ship one. INFRO runs every decision feature on whichever of them you connect, translating each provider's wire into one shape, so a policy, a guardrail check or a route classifier names a model and nothing else changes.
Every call runs on your own account with that provider, which bills it. INFRO charges nothing per decision; the estimated provider cost of each call is on its row in the request log, separate from your INFRO subscription.
The models
| Model | Connect | Images |
|---|---|---|
typesafe/jev-1.13 — Jev 1.13 | TypeSafe | Text and JSON only |
openai/gpt-6-luna-decisions — GPT-6 Luna (Decisions) | OpenAI (the Decisions API) | Data URLs |
cloudflare/clef — Clef | Cloudflare Workers AI (API token and account ID) | Data URLs, up to 4 |
cloudflare/clef-flash — Clef Flash | Cloudflare Workers AI | Data URLs, up to 4 |
cloudflare/clef-omni — Clef Omni | Cloudflare Workers AI | Data URLs, up to 4; audio and video are not offered yet |
perplexity/pplx-decider-v1.1-27b — pplx-decider v1.1 | Perplexity | Data URLs |
inception/mercury-decide — Mercury Decide | Inception | Text and JSON only |
liquid/d1 — d1 | Liquid AI | Data URLs, up to 8 |
upstage/solar-decide — Solar Decide | Upstage (in beta at Upstage) | Text and JSON only |
upstage/solar-decide-flash — Solar Decide Flash | OpenRouter only | Text and JSON only |
microsoft/microsoft-decision-1 — Microsoft-Decision-1 | Azure OpenAI (a Foundry deployment, mapped to its deployment name) | Text and JSON only |
An OpenRouter connection reaches every one of them too, on OpenRouter's own System One endpoint. Connect both a provider and OpenRouter, or two providers' models in a route, and a decision fails over between them like any other request.
Name a model by its slug in a decision policy, a guardrail's classifier checks, a route's prompt classifier, POST /v1/rerank, POST /v1/systemone or POST /v1/decisions. On a decision endpoint, gpt-6-luna and openai/gpt-6-luna mean GPT-6 Luna's decisions; on chat completions they mean the chat model. A decision model named on chat completions is refused with 400 decision_model.
POST /v1/decisions in OpenAI's dialect
OpenAI's SDK (client.decisions.create) works against INFRO with the base URL and the key changed. The request runs on your own connections with every INFRO policy applied — key scopes, budgets, routes and failover, the request log — and the model need not be OpenAI's: any decision model answers in OpenAI's shape.
import os
from openai import OpenAI
# The INFRO gateway key, not the OpenAI key: that stays in your INFRO connection.
client = OpenAI(base_url="https://api.infro.io/v1", api_key=os.environ["INFRO_API_KEY"])
decision = client.decisions.create(
model="gpt-6-luna", # or any decision model: "cloudflare/clef-flash", "typesafe/jev-1.13"
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": "other", "description": "Requests outside these categories."},
],
}
],
)
print(decision.answers[0]){
"id": "req_…",
"object": "decision",
"model": "gpt-6-luna",
"answers": [
{
"type": "choice",
"name": "department",
"choice": "billing",
"probabilities": [
{ "value": "billing", "probability": 0.95 },
{ "value": "technical", "probability": 0.03 },
{ "value": "other", "probability": 0.02 }
],
"confidence": 0.93
}
],
"usage": {
"input_tokens": 61,
"input_tokens_details": { "cached_tokens": 0, "cache_write_tokens": 0 },
"output_tokens": 0,
"output_tokens_details": { "reasoning_tokens": 0 },
"total_tokens": 61,
"cost": 0.0000061,
"price_source": "list"
},
"route": "primary",
"route_name": null,
"provider": "openai",
"connection": "conn_…"
}inputis a string, or user messages ofinput_textandinput_imageparts (at most 128 images). An image reaches only a model that sees images; a request whose model reads text only moves to its next fallback, and with none left is refused with400 images_unsupported. Every decision model takes images only as inline data URLs: Cloudflare, Perplexity and Liquid AI are refused a link with400 image_url_unsupportedbefore any call, and OpenAI's Decisions API refuses one with its own400.questionsarepredicate,choiceandscore, as OpenAI documents them. On a System One model a predicate is a yes/no question, a choice's options are keyed by their text (a boolean value comes back as the boolean), and a score's level readslabel: description.- A question no model answered readably comes back as OpenAI's
{"type": "refusal"}; an unnamed question is answered withname: null. safety_identifieris passed to OpenAI and to no other provider.- Responses add
id,usage.cost,usage.price_source,route,route_name,providerandconnectionto OpenAI's fields. POST /v1/decisions/{policy}is a saved decision policy: a different route under the same path.
Images on POST /v1/systemone
POST /v1/systemone takes TypeSafe's body and, for the models that see images, a top-level images array — data URLs, or {content_type, base64} — the extension Cloudflare and Liquid AI add to it. Each provider receives the images in its own form.
Errors
| Status | Code | When |
|---|---|---|
| 400 | decision_model | A decision model was named on chat completions or another endpoint that is not a decision endpoint. |
| 400 | images_unsupported | The request carries an image and no model it could reach sees images. |
| 400 | image_url_unsupported | The model takes images only inline, and the request linked one. |
| 400 | invalid_request | The provider rejected the question set; the message carries its words. Not retried on another key: every key of a provider validates alike. |
| 503 | no_provider_connected | No connection serves the model. Connect its provider, or OpenRouter. |