# Decisions API — Typed Decisions with Jev and Other Decision Models Source: https://ofox.ai/docs/api/decisions The Decisions API lets a model make **typed decisions** about a piece of content instead of generating text. You provide the content to evaluate and a set of questions, and the model returns a structured answer for each one: the probability of yes/no, a single pick from a list of options, or a score on a graded scale, along with per-option probabilities and a confidence value. It is built for classification, routing, moderation, scoring, filtering, and any other case where your code needs to branch directly on the result. The model can only answer with the options and levels you define, never anything outside them, so there is no free-form text to parse. When using a decision model for the first time, it is recommended to read the [Jev guide](https://ofox.ai/docs/develop/models/jev) first, which covers quick start, question design, response fields, and scenario examples. This page is the field-level reference. ## Endpoints All three endpoints serve the same models with the same pricing; they differ only in request and response format. Pick the one that matches your client: | Endpoint | Format | Best for | |---|---|---| | `POST https://api.ofox.ai/v1/decisions` | OfoxAI unified format (questions as an array) | cURL, any HTTP client, the [OpenAI SDK](#openai-sdk) | | `POST https://api.ofox.ai/openai/v1/decisions` | Same as above | Projects that already set the OpenAI SDK `base_url` to `https://api.ofox.ai/openai/v1` | | `POST https://api.ofox.ai/typesafe/v1/systemone` | Official TypeSafe System One format (questions as an object) | The [official TypeSafe SDK](#typesafe-sdk), or code written against the official TypeSafe docs | Authentication works the same as the other endpoints: `Authorization: Bearer `. Only non-streaming requests are supported; each request returns all answers synchronously. ## Available models | Model ID | Description | |---|---| | `typesafe/jev-latest` | TypeSafe Jev, a dedicated decision model. Text or JSON input only, 64K context. Can be shortened to `jev-latest` | | `openai/gpt-6-luna` | Works on all three endpoints; can read images (pass `input_image` in the [message array](#input)) | For the full list of models that support this API, see the [models page](https://ofox.ai/models?output=decisions). Calling it with a regular chat model (such as `openai/gpt-4o`) returns `endpoint_not_supported`. ## Unified format: `/v1/decisions` ### Request parameters | Parameter | Type | Required | Description | |---|---|---|---| | `model` | string | ✅ | Model ID, e.g. `typesafe/jev-latest` | | `input` | string \| object \| array | ✅ | The content to evaluate; see [Input format](#input) | | `questions` | array | ✅ | List of questions, each a [question object](#questions). Answers are returned in the same order | | `safety_identifier` | string | — | A stable end-user ID; only applies to OpenAI models | ### Input format `input` accepts three forms: - **Text**: a single string. ```json "input": "Customer message: I was charged twice, nobody has answered my two emails, and I need a refund today." ``` - **Structured data**: a JSON object, passed to the model as-is. Questions can reference field names in backticks, such as `` `message` ``. ```json "input": { "ticket_id": 4812, "message": "I was charged twice.", "plan": "pro" } ``` - **Message array**: `[{ "role", "content" }]`, where `content` is a string or a list of content blocks. Only `input_text` and `input_image` blocks are supported. Images only work with models that can read them. **Jev does not read images**; sending one returns 400. ### Question object | Field | Applies to | Description | |---|---|---| | `type` | All | `predicate` (yes/no), `choice` (single choice), or `score` (graded score) | | `name` | All | Optional but recommended. Echoed back in the answer so you can match them up. Must be unique within a request | | `instructions` | All | The question itself. A string, or a JSON object containing the question and reference data | | `criteria` | `predicate` | Optional. `{ "true": "...", "false": "..." }` describing what yes and no each mean | | `choices` | `choice` | Required. Array of options, each `{ "value": "billing", "description": "Charges, refunds" }`; `description` is optional. Up to 255 options | | `levels` | `score` | Required. Array of levels ordered from lowest to highest, each `{ "label": "low", "description": "..." }`. 2–10 levels | ### Request examples **cURL** ```bash filename="Terminal" curl https://api.ofox.ai/v1/decisions \ -H "Authorization: Bearer $OFOX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "typesafe/jev-latest", "input": "Customer message: I was charged twice, nobody has answered my two emails, and I need a refund today.", "questions": [ { "type": "predicate", "name": "is_frustrated", "instructions": "Is the customer frustrated?" }, { "type": "choice", "name": "category", "instructions": "Which category does this ticket belong to?", "choices": [ { "value": "billing", "description": "Charges and refunds" }, { "value": "bug", "description": "Product issue" }, { "value": "other", "description": "Other" } ] }, { "type": "score", "name": "urgency", "instructions": "How quickly does a human need to respond?", "levels": [ { "label": "low", "description": "Within a week is fine" }, { "label": "medium", "description": "Should respond soon" }, { "label": "high", "description": "Must respond today" } ] } ] }' ``` **Python** ```python filename="decisions.py" showLineNumbers import os from openai import OpenAI client = OpenAI(base_url="https://api.ofox.ai/v1", api_key=os.environ["OFOX_API_KEY"]) result = client.decisions.create( model="typesafe/jev-latest", input="Customer message: I was charged twice, nobody has answered my two emails, and I need a refund today.", questions=[ {"type": "predicate", "name": "is_frustrated", "instructions": "Is the customer frustrated?"}, { "type": "choice", "name": "category", "instructions": "Which category does this ticket belong to?", "choices": [ {"value": "billing", "description": "Charges and refunds"}, {"value": "bug", "description": "Product issue"}, {"value": "other", "description": "Other"}, ], }, ], ) is_frustrated, category = result.answers print(is_frustrated.probability) # 0.97 print(category.choice) # billing ``` **TypeScript** ```typescript filename="decisions.ts" showLineNumbers const res = await fetch('https://api.ofox.ai/v1/decisions', { method: 'POST', headers: { Authorization: `Bearer ${process.env.OFOX_API_KEY}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'typesafe/jev-latest', input: 'Customer message: I was charged twice, nobody has answered my two emails, and I need a refund today.', questions: [ { type: 'predicate', name: 'is_frustrated', instructions: 'Is the customer frustrated?' } ] }) }) const { answers } = await res.json() console.log(answers[0].probability) ``` ### Response format ```json { "model": "typesafe/jev-latest", "answers": [ { "type": "predicate", "name": "is_frustrated", "probability": 0.97 }, { "type": "choice", "name": "category", "choice": "billing", "confidence": 1, "probabilities": [ { "value": "billing", "probability": 1 }, { "value": "bug", "probability": 0 }, { "value": "other", "probability": 0 } ] }, { "type": "score", "name": "urgency", "score": 2, "confidence": 1, "legend": { "0": "low: Within a week is fine", "1": "medium: Should respond soon", "2": "high: Must respond today" }, "probabilities": [ { "value": 0, "label": "low", "probability": 0 }, { "value": 1, "label": "medium", "probability": 0 }, { "value": 2, "label": "high", "probability": 1 } ] } ], "usage": { "prompt_tokens": 407, "completion_tokens": 72, "total_tokens": 479 } } ``` `answers` maps one-to-one to `questions`, in the same order. Fields for each answer type: | Type | Field | Description | |---|---|---| | `predicate` | `probability` | Probability that the answer is yes, 0–1 | | `choice` | `choice` | The option with the highest probability | | | `probabilities` | Probability for each option; sums to 1 | | | `confidence` | How certain the model is about this decision, 0–1; see [Probability and confidence](#confidence) | | `score` | `score` | Probability-weighted level value, starting from 0. **It can fall between two levels** (e.g. 1.43) | | | `legend` | Mapping from level index to description | | | `probabilities` | Probability for each level | | | `confidence` | Same as above | ## TypeSafe native format: `/typesafe/v1/systemone` Request and response formats match the [official TypeSafe API](https://docs.typesafe.ai/api), so you can use the TypeSafe SDK directly or migrate existing code. Compared with the unified format, the differences are: | | Unified format `/v1/decisions` | TypeSafe native format | |---|---|---| | Content to evaluate | `input` | `state` | | Questions | Array, identified by `name` | Object, where each key is the question ID | | Yes/no questions | `type: "predicate"`, answer field `probability` | `type: "noul"`, answer field `noul` | | Options and levels | `choices` / `levels` arrays | Both go in `criteria`: an `{option: description}` object for choice, an array of description strings for score | | Usage fields | `prompt_tokens` / `completion_tokens` | `input_tokens` / `output_tokens` | ```bash filename="Terminal" curl https://api.ofox.ai/typesafe/v1/systemone \ -H "Authorization: Bearer $OFOX_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "jev-latest", "state": "Help! My payouts have been failing for 3 days.", "questions": { "is_urgent": { "type": "noul", "instructions": "Does this convey urgency?" }, "department": { "type": "choice", "instructions": "Which team should handle this?", "criteria": { "billing": "Payments, invoicing, refunds", "technical": "Bugs, outages, integrations", "sales": "Pricing, upgrades" } }, "frustration": { "type": "score", "instructions": "How frustrated is the customer?", "criteria": ["Calm", "Frustrated", "Very angry"] } } }' ``` ```json { "model": "typesafe/jev-latest", "answers": { "is_urgent": { "type": "noul", "noul": 0.95 }, "department": { "type": "choice", "choice": "billing", "confidence": 0.85, "probabilities": { "billing": 0.9, "technical": 0.1, "sales": 0 } }, "frustration": { "type": "score", "score": 1.03, "confidence": 0.95, "legend": { "0": "Calm", "1": "Frustrated", "2": "Very angry" }, "probabilities": { "0": 0, "1": 0.97, "2": 0.03 } } }, "usage": { "input_tokens": 399, "output_tokens": 73 } } ``` ## SDK integration ### OpenAI SDK The official OpenAI SDK provides `client.decisions.create()`. Point `base_url` at `https://api.ofox.ai/v1` and use the [unified format](#unified) for the request body. See the [Python tab](#example) above for an example. You need an SDK version that includes the Decisions API (tested with `openai` Python 3.31.0). ### TypeSafe SDK With the official TypeSafe SDK, set `base_url` to `https://api.ofox.ai/typesafe` and replace `api_key` with your OfoxAI API Key. The rest of your code stays the same: **Python** ```python filename="typesafe_sdk.py" showLineNumbers # pip install typesafe-sdk import os from typesafe_sdk import TypeSafeClient, Noul, Choice, Score with TypeSafeClient(base_url="https://api.ofox.ai/typesafe", api_key=os.environ["OFOX_API_KEY"]) as client: r = client.system_one( model="jev-latest", state={"message": "I was charged twice, nobody has answered my two emails, and I need a refund today."}, questions={ "urgent": Noul(instructions="Is the customer asking for this to be handled today?"), "dept": Choice( instructions="Which team should handle this?", criteria={"billing": "Charges, invoices, refunds", "technical": "Bugs, integrations", "sales": "Pricing, upgrades"}, ), "anger": Score(instructions="How upset is the customer?", criteria=["Calm", "Upset but polite", "Very angry"]), }, ) print(r.nouls["urgent"].noul) # 0.98 print(r.choices["dept"].choice) # billing print(r.scores["anger"].score) # 1.56 ``` **TypeScript** ```typescript filename="typesafe_sdk.ts" showLineNumbers // npm install @typesafe-ai/sdk import { TypeSafeClient, choice } from '@typesafe-ai/sdk' const client = new TypeSafeClient({ baseURL: 'https://api.ofox.ai/typesafe', apiKey: process.env.OFOX_API_KEY }) const response = await client.systemOne({ model: 'jev-latest', state: { message: 'I was charged twice and I need a refund today.' }, questions: { category: choice('Which category does this ticket belong to?', { billing: null, technical: null, other: null }) } }) ``` **Environment variables** ```bash filename="Terminal" # Both SDKs read these two environment variables, so no code changes are needed export TYPESAFE_BASE_URL="https://api.ofox.ai/typesafe" export TYPESAFE_API_KEY="$OFOX_API_KEY" ``` ## Probability and confidence - **Probability** answers "what is the answer": `probability` and `probabilities` are the model's calibrated probabilities for each possible answer. - **Confidence** answers "should I trust this decision": `confidence` is derived from the probability distribution and is higher when the distribution is more concentrated. If two options each sit at 0.5, `choice` still returns one of them, but `confidence` will be low. A common pattern is to set a threshold in your code: act automatically when confidence is high, and route to a human or a stronger model for review when it is low. ## Pricing Billing is per token; see the [model page](https://ofox.ai/models/typesafe/jev-latest) for rates. `typesafe/jev-latest` **bills input tokens only**; output is not billed (`completion_tokens` / `output_tokens` are still returned). All questions in a request share the same input, so **putting multiple questions in one request** is much cheaper and faster than splitting them across separate calls. ## Limits | Item | Limit | |---|---| | Request body | Up to 32 MiB | | Context | 64K tokens for Jev; exceeding it returns 400 | | Choice options | Up to 255 | | Score levels | 2–10 | | JSON nesting | Up to 64 levels each for `input`/`state`, `instructions`, and `criteria` | | Streaming | Not supported | ## Common errors | HTTP · type | Error message | Cause | Fix | |---|---|---|---| | 400 `invalid_request_error` | `question "a" has kind choice and requires criteria options` | A choice question has no `choices` | Add the options array | | 400 `invalid_request_error` | `question name "a" is used more than once` | Two questions share the same `name` | Use unique names | | 400 `invalid_request_error` | `Too many score levels. Must have at most 10 levels.` | A score question has more than 10 levels | Merge levels, or split into multiple questions | | 400 `invalid_request_error` | `decision input carries an "input_image" part, which Typesafe cannot read` | An image was sent to Jev | Remove the image, or switch to a model that can read images | | 400 `invalid_request_error` | `Invalid request payload` | The TypeSafe native format (`state` + object-style `questions`) was sent to `/v1/decisions` | Switch to the unified format, or call `/typesafe/v1/systemone` instead | | 400 `endpoint_not_supported` | `Model 'openai/gpt-4o' does not support the /v1/decisions endpoint on this platform.` | The model is not a decision model | Use one of the [available models](#models) | | 404 `model_not_found` | `Model 'typesafe/jev-9' not found` | Wrong model ID | Use `typesafe/jev-latest` | | 404 `invalid_request_error` | `Unsupported API endpoint.` | The path was written as `/v1/systemone` | The TypeSafe native endpoint is `/typesafe/v1/systemone` | For other general errors, see [Error handling](https://ofox.ai/docs/develop/guides/error-handling).