Decisions
POST /v1/decisions answers typed questions about one input: the probability
that a condition holds, a pick from fixed options, or a score against ordered
levels. It mirrors OpenAI's Decisions API, so the openai SDK's
client.decisions.create works against Ingram Cloud unchanged. Use it to route
tickets, filter content, triage photos or rate severity without running a
conversation.
The call is metered like any model op. Nothing is stored.
Models
| Model | Input | Key |
|---|---|---|
gpt-6-luna (openai.gpt-6-luna) | text and images | your project's OpenAI key, else Ingram-hosted |
typesafe-ai.jev-latest, typesafe-ai.jev-1.13.0 | text | your project's TypeSafe key only |
On GPT-6 Luna, requests and answers pass through unchanged, so OpenAI's Decisions
guide applies field
for field. Jev answers on the same wire. jev-latest moves when TypeSafe ships
a new version; pin jev-1.13.0 for output that stays stable.
Ask questions
# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/decisions \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{
"model": "gpt-6-luna",
"input": "My card was charged twice.",
"questions": [
{
"type": "choice",
"name": "route",
"instructions": "Route this ticket.",
"choices": [
{ "value": "billing", "description": "Payment problems" },
{ "value": "shipping", "description": "Delivery problems" }
]
},
{
"type": "score",
"name": "urgency",
"instructions": "How urgent is this ticket?",
"levels": [{ "label": "can wait" }, { "label": "today" }, { "label": "now" }]
},
{ "type": "predicate", "name": "refund", "instructions": "The customer asks for a refund." }
]
}'
With the OpenAI SDK, point it at Ingram Cloud:
import OpenAI from "openai";
const client = new OpenAI({
baseURL: "https://api.cloud.ingram.tech/v1",
apiKey: process.env.IC_TOKEN,
defaultHeaders: { "IC-Api-Version": "2026-05-01" },
});
const decision = await client.decisions.create({ model: "gpt-6-luna", input, questions });
type | Takes | Answer |
|---|---|---|
predicate | instructions: the condition | probability that it holds, from 0 to 1 |
choice | choices: { value, description? }, at least one | choice, one of your values, and probabilities over every value |
score | levels: { label, description? }, lowest first, at least two | score, a fractional level index from 0, and probabilities per level |
Use choice for unordered categories and score for ordered levels. A choice
value is a string or a boolean, and comes back with its type.
Questions in one request are answered independently, against the same input. When one question depends on another's answer, ask it in a second request.
Images
On GPT-6 Luna, input can be a list of user messages whose content mixes
input_text and input_image parts. An image is an inline base64 data URL;
hosted URLs and file ids are refused.
"input": [
{
"role": "user",
"content": [
{ "type": "input_text", "text": "Customer photo of the parcel:" },
{ "type": "input_image", "image_url": "data:image/jpeg;base64,…" }
]
}
]
Jev takes text only: an image is 422 invalid_request.
Read the answers
{
"model": "gpt-6-luna",
"answers": [
{ "type": "choice", "name": "route", "choice": "billing", "confidence": 0.97,
"probabilities": [{ "value": "billing", "probability": 0.99 }, { "value": "shipping", "probability": 0.01 }] },
{ "type": "score", "name": "urgency", "score": 1.2, "confidence": 0.9,
"probabilities": [{ "value": 0, "label": "can wait", "probability": 0.1 }, { "value": 1, "label": "today", "probability": 0.6 }, { "value": 2, "label": "now", "probability": 0.3 }] },
{ "type": "predicate", "name": "refund", "probability": 0.8 }
],
"usage": {
"input_tokens": 58,
"input_tokens_details": { "cached_tokens": 0, "cache_write_tokens": 0 },
"output_tokens": 0,
"output_tokens_details": { "reasoning_tokens": 0 },
"total_tokens": 58
}
}
answers follows the order of questions, each with the name you gave or
null. confidence on choice and score answers is the model's confidence in
that answer, not a probability. Act on a predicate by comparing probability
with a threshold you pick for the cost of each mistake.
A question the model declines comes back as { "type": "refusal", "name": … }
in its place. The call is still metered.
Limits & errors
On GPT-6 Luna, OpenAI's limits apply, including at most 128 images per request. On Jev, TypeSafe accepts at most 255 choices, 10 levels, 64k tokens per request, and 32k tokens for the input plus the longest question.
| Status | Code | When |
|---|---|---|
422 | invalid_request | The body breaks the shape above, or Jev gets an image or a safety_identifier. |
422 | model_kind_mismatch | model is not a classifier model. |
422 | model_key_missing | Jev, and your project has no TypeSafe key. |
400 | model_request_failed | The provider refused the request: a bad key, or a limit above. The message carries the provider's reason. |
402 | budget_exceeded, card_required, insufficient_credits | As for runs; only a project-wide budget applies. See Usage and Billing. |
429 | rate_limit_exceeded | The provider is rate-limiting the key. Retry after the Retry-After seconds. |
500 | evaluation_failed | Any other failure. |
Keys
GPT-6 Luna runs on your project's OpenAI key, else on the Ingram-hosted key, as for any OpenAI model. See Keys.
Jev runs on your project's TypeSafe key only. Add it in Settings → Models, or over the API:
# Authorization: tenant-admin token (server-side only)
curl -X PUT https://api.cloud.ingram.tech/v1/tenant/model_keys/typesafe-ai \
-H "Authorization: Bearer $IC_ADMIN_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{ "api_key": "…" }'
TypeSafe bills your account for the tokens; your wallet is charged the platform fee alone. See Your own keys.
The call needs a tenant token with the runs:write scope. A smith token is
refused with 403 tenant_token_required. See Auth & tokens.
The AI Gateway wire
POST /v1/evaluate answers the same questions on the AI Gateway's
/v1/evaluate wire, whose fields are the AI SDK's decision-model fields. It
takes text only, and model defaults to typesafe-ai.jev-latest.
# Authorization: tenant-admin token (server-side only)
curl https://api.cloud.ingram.tech/v1/evaluate \
-H "Authorization: Bearer $IC_TOKEN" \
-H "IC-Api-Version: 2026-05-01" \
-H "Content-Type: application/json" \
-d '{
"state": "My card was charged twice.",
"questions": {
"route": {
"type": "choice",
"instructions": "Route this ticket.",
"criteria": { "billing": "payment problems", "shipping": "delivery problems" }
},
"refund": { "type": "boolean", "instructions": "Does the customer ask for a refund?" }
}
}'
/v1/decisions | /v1/evaluate |
|---|---|
input | state: a string, a JSON object, or a JSON array |
questions, a list with name | questions, a map keyed by your id |
predicate | boolean, with optional criteria: { "true": …, "false": … } |
choices: [{ value, description }] | criteria: { value: description } |
levels: [{ label, description }] | criteria: [description, …] |
answers, a list in question order | answers, a map keyed by id |
confidence on each answer | providerMetadata.<provider>.confidence, keyed by id |
{ "type": "refusal", "name": … } | { "type": "refusal" } |
Scores and probabilities are rounded to the places in rounding.