Decisions API

Calibrated probabilities for yes/no, choice, score, and tool questions about text, images, audio, and video

Decision models answer typed questions about content you send. You send a state (text, JSON, images, audio, or video) and a set of questions, and each answer comes back as probabilities with a confidence value. Decision models never generate text.

Decision models are available through the API at POST /v1/decisions.

Endpoint

POST https://api.empiriolabs.ai/v1/decisions

Authenticate with your API key in the Authorization header:

Authorization: Bearer $EMPIRIOLABS_API_KEY

The full request and response schema is in the API reference.

Models

ModelInputBest for
decide-1Text, JSON, images, videoQuestions about text, JSON, images, and video
decide-1-omniText, JSON, images, audio, videoQuestions that also depend on speech or other audio

Both models take the same request and return the same response shape.

Your first request

This request asks three questions about one support message: whether it is urgent, which team should handle it, and how frustrated the customer is.

curl https://api.empiriolabs.ai/v1/decisions \
-H "Authorization: Bearer $EMPIRIOLABS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "decide-1",
"state": {
"channel": "email",
"message": "I was charged twice for my subscription this month. This is the third time I have written about it. Please refund the duplicate charge today."
},
"questions": {
"is_urgent": {
"type": "noul",
"instructions": "Does the customer need a resolution today?"
},
"department": {
"type": "choice",
"instructions": "Which team should handle this message?",
"criteria": {
"billing": "Charges, refunds, and invoices",
"technical": "Bugs, errors, and outages",
"sales": "Plans, pricing, and upgrades"
}
},
"frustration": {
"type": "score",
"instructions": "How frustrated is the customer?",
"criteria": ["Calm", "Frustrated", "Very angry"]
}
}
}'
Response
{
"model": "decide-1",
"answers": {
"is_urgent": {"type": "noul", "noul": 0.9412, "confidence": 0.8824},
"department": {
"type": "choice",
"choice": "billing",
"probabilities": {"billing": 0.88, "technical": 0.11, "sales": 0.01},
"confidence": 0.82
},
"frustration": {
"type": "score",
"score": 1.05,
"legend": {"0": "Calm", "1": "Frustrated", "2": "Very angry"},
"probabilities": {"0": 0.02, "1": 0.91, "2": 0.07},
"confidence": 0.865
}
},
"usage": {
"input_tokens": 212,
"output_tokens": 0,
"input_tokens_details": {"text_tokens": 212, "image_tokens": 0, "audio_tokens": 0, "video_tokens": 0}
}
}

Request

FieldTypeRequiredDescription
modelstringYesdecide-1 or decide-1-omni. An unknown ID returns 404 model_not_found.
statestring, object, or arrayYesThe content to decide about. Media nodes can appear anywhere inside it.
questionsobjectYes1 to 128 questions, keyed by an ID you choose.

Question IDs. Answers come back under the same IDs you send. IDs are not shown to the model, so name them for your own code. An ID cannot contain control characters.

Instructions. Every question has a type and instructions. instructions is usually a string. It can also be an object or an array that holds the question in one field and the data it refers to in others.

Independent answers. Each question is answered independently of the other questions in the request.

Question types

Yes or no (noul)

noul asks a yes/no question. The answer’s noul field is the probability that the answer is yes. Add criteria with true and false descriptions when the line between yes and no needs defining:

"refund_request": {
"type": "noul",
"instructions": "Is the customer asking for a refund?",
"criteria": {
"true": "The message asks for money back.",
"false": "The message reports a problem without asking for money back."
}
}
Answer
"refund_request": {"type": "noul", "noul": 0.9721, "confidence": 0.9442}

Choice (choice)

choice picks one of 2 to 255 named options. criteria maps each option name to a description, or to null when the name is enough. The answer’s choice is the most likely option, and probabilities holds the probability of every option. Option names are returned unchanged.

A choice between exactly two yes/no options, such as yes and no, returns the same probability as the equivalent noul question, so the two never disagree.

Score (score)

score rates the state on an ordered scale of 2 to 10 levels, listed lowest first in criteria. Levels are numbered from 0 in that order. The answer’s score is the expected level (the probability-weighted average of the level numbers), legend maps each level number to its description, and probabilities holds the probability of each level.

In the first request above, frustration returned a score of 1.05, with most of the probability on level 1 (Frustrated).

Tool (tool)

tool chooses which function to call. tools is an array of 1 to 254 function definitions in the same format as tools on Chat Completions.

  • Function choice. The answer’s tool is the most likely function, and probabilities holds the probability of each.
  • No function. With allow_none set to true (the default), none is one more option, and tool is null when none is the most likely answer.
  • Arguments. Every enum and boolean argument of every function gets its own distribution under arguments: which value the argument should take if that function is called. enum arguments return probabilities and boolean arguments return probability_true.
  • Open arguments. Other arguments, such as free text, are listed under open_arguments. Decision models do not fill them; produce them separately if you need them.
Request
{
"model": "decide-1",
"state": "Customer: Can you move my Thursday appointment to Friday morning?",
"questions": {
"next_action": {
"type": "tool",
"instructions": "Which function should handle this request?",
"tools": [
{
"type": "function",
"function": {
"name": "reschedule_appointment",
"description": "Move an existing appointment to a new day and time.",
"parameters": {
"type": "object",
"properties": {
"day": {"type": "string", "enum": ["monday", "tuesday", "wednesday", "thursday", "friday"]},
"time_of_day": {"type": "string", "enum": ["morning", "afternoon", "evening"]},
"notify_by_sms": {"type": "boolean"},
"note": {"type": "string"}
},
"required": ["day", "time_of_day"]
}
}
},
{
"type": "function",
"function": {
"name": "cancel_appointment",
"description": "Cancel an existing appointment.",
"parameters": {"type": "object", "properties": {}}
}
}
]
}
}
}
Answer
"next_action": {
"type": "tool",
"tool": "reschedule_appointment",
"probabilities": {"reschedule_appointment": 0.96, "cancel_appointment": 0.01, "none": 0.03},
"confidence": 0.94,
"arguments": {
"reschedule_appointment": {
"day": {
"value": "friday",
"probabilities": {"monday": 0.001, "tuesday": 0.002, "wednesday": 0.007, "thursday": 0.04, "friday": 0.95},
"confidence": 0.9375
},
"time_of_day": {
"value": "morning",
"probabilities": {"morning": 0.93, "afternoon": 0.05, "evening": 0.02},
"confidence": 0.895
},
"notify_by_sms": {"value": false, "probability_true": 0.12, "confidence": 0.76}
}
},
"open_arguments": {"reschedule_appointment": ["note"]}
}

A tool question counts once toward the 128-question limit. Its functions can have at most 64 enum and boolean arguments in total. A request can evaluate at most 512 questions in total, where each tool question counts as one plus one per enum or boolean argument.

Confidence

Every answer includes confidence, from 0 to 1, which measures how concentrated the distribution is. 0 means every option is equally likely; 1 means all probability is on one option. For n options whose highest probability is p, confidence is (n * p - 1) / (n - 1). For a noul answer it is |2p - 1|.

Use confidence as a threshold: act automatically on high-confidence answers and send the rest for review.

Probabilities are rounded to at most 8 decimal places and sum to 1.

Media input

Put images, audio, and video anywhere inside state as media nodes. A media node is an object with a type of image, audio, or video, and either a public https:// URL in url, or the file as base64 in data with its MIME type in mime:

{"type": "image", "url": "https://example.com/listing-photo.png"}
{"type": "image", "data": "<base64>", "mime": "image/png"}
Request with an image
{
"model": "decide-1",
"state": {
"listing_title": "Three-seat leather sofa, like new",
"photo": {"type": "image", "url": "https://example.com/listing-photo.png"}
},
"questions": {
"matches_title": {"type": "noul", "instructions": "Does the photo show the item described in listing_title?"},
"condition": {"type": "score", "instructions": "Rate the condition of the item in the photo.", "criteria": ["Damaged", "Worn", "Good", "Like new"]}
}
}

decide-1 accepts images and video. Audio requires decide-1-omni; audio sent to decide-1 returns 400 unsupported_media.

MediaPer requestPer item
ImageUp to 1620 MB
AudioUp to 825 MB and 10 minutes
VideoUp to 4100 MB and 10 minutes

A media URL must be publicly reachable over https://. Other URLs return 400 invalid_media_url; send the file as data instead.

Limits

  • Questions. 1 to 128 per request. A tool question counts once toward that limit.
  • Evaluated questions. At most 512 per request, where each tool question counts as one plus one per enum or boolean argument. More returns 422 too_many_questions.
  • Tokens. The state plus the longest question can be up to 60,000 tokens.
  • Choice options. 2 to 255 per choice question.
  • Score levels. 2 to 10 per score question.
  • Functions. 1 to 254 per tool question, with at most 64 enum and boolean arguments in total.
  • Media. See the table in Media input.

Usage and billing

Decision requests are billed on input tokens only. usage.input_tokens counts what you send:

  • The state, once per request, however many questions refer to it.
  • The text of each question.
  • Each media item: at most 640 tokens per image, at most 256 tokens per video frame, and about 13 tokens per second of audio.

A tool question counts its own instructions and tools once, however many arguments it evaluates.

Nothing else is added to the count, and output_tokens is always 0. input_tokens_details splits input_tokens into text_tokens, image_tokens, audio_tokens, and video_tokens.

Because the state is counted once per request, asking several questions about the same state in one request costs less than sending one request per question. Current rates are on each model page and on the pricing page.

Errors

Errors return a JSON body with a code and a message. Validation messages name the offending field:

{"error": {"code": "too_many_questions", "message": "At most 128 questions are accepted per request."}}
StatuscodeMeaning
400invalid_media_urlA media URL was refused. Use a publicly reachable https:// URL, or send the file as data.
400unsupported_mediaThe model does not accept a media type in the state, for example audio on decide-1.
401invalid_api_keyThe API key is missing or invalid.
402insufficient_creditsThe account balance is too low for the request.
404model_not_foundThe model ID is unknown.
413request_too_largeThe request body is too large. Send large media as url instead of data.
422invalid_requestThe request does not match the schema. The message names the field.
422state_too_largeThe state plus the longest question is over 60,000 tokens.
422too_many_questionsThe request has more than 128 questions, or evaluates more than 512.
422invalid_mediaA media item is invalid or over a limit in Media input.
429rate_limit_exceededAn account rate limit was reached. Retry after the number of seconds in Retry-After.
503model_unavailableThe model is temporarily unavailable. Retry after the number of seconds in Retry-After; the request is safe to retry.

See Errors and Status Codes for how the API reports errors across endpoints.

MCP

MCP clients can call the Decisions API as the decide tool on the EmpirioLabs MCP server.