Ingram Cloud

Documentation

Decisions

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

ModelInputKey
gpt-6-luna (openai.gpt-6-luna)text and imagesyour project's OpenAI key, else Ingram-hosted
typesafe-ai.jev-latest, typesafe-ai.jev-1.13.0textyour 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 });
typeTakesAnswer
predicateinstructions: the conditionprobability that it holds, from 0 to 1
choicechoices: { value, description? }, at least onechoice, one of your values, and probabilities over every value
scorelevels: { label, description? }, lowest first, at least twoscore, 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.

StatusCodeWhen
422invalid_requestThe body breaks the shape above, or Jev gets an image or a safety_identifier.
422model_kind_mismatchmodel is not a classifier model.
422model_key_missingJev, and your project has no TypeSafe key.
400model_request_failedThe provider refused the request: a bad key, or a limit above. The message carries the provider's reason.
402budget_exceeded, card_required, insufficient_creditsAs for runs; only a project-wide budget applies. See Usage and Billing.
429rate_limit_exceededThe provider is rate-limiting the key. Retry after the Retry-After seconds.
500evaluation_failedAny 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
inputstate: a string, a JSON object, or a JSON array
questions, a list with namequestions, a map keyed by your id
predicateboolean, with optional criteria: { "true": …, "false": … }
choices: [{ value, description }]criteria: { value: description }
levels: [{ label, description }]criteria: [description, …]
answers, a list in question orderanswers, a map keyed by id
confidence on each answerproviderMetadata.<provider>.confidence, keyed by id
{ "type": "refusal", "name": … }{ "type": "refusal" }

Scores and probabilities are rounded to the places in rounding.