Decisions API

Decisions API

Most model calls hand you a paragraph and leave you to pull a decision out of it: prompt for JSON, parse it, validate it, retry when it drifts. The Decisions API removes that layer. You send the state your application is in plus the questions you need answered, and each answer comes back already typed — a boolean-like likelihood, one of your named options, or a position on your own scale — with a probability attached.

It is built for the decision points inside software rather than for conversation: routing a ticket, gating a tool call, triaging a queue, scoring a submission before a human sees it.

POST https://api.beatapi.io/v1/systemone

This is TypeSafe’s own endpoint path, and the request and response bodies are theirs too — a client written against the TypeSafe API or any of its SDKs reaches this by changing the base URL and the model name, with nothing else to adapt. POST /v1/decisions is kept as an alias.

ModelChoose it for
jev-1.13Fast typed decisions from TypeSafe. 32K context, answers in a few hundred milliseconds.

This endpoint is not /v1/chat/completions, and jev-1.13 is not available there. There are no messages in the request and no choices in the response — a decision model does not generate text.

Quick start

curl https://api.beatapi.io/v1/systemone \
-H "Authorization: Bearer $BEATAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "jev-1.13",
"state": "Task: clean up inactive accounts before the quarterly report.\nProposed tool call: delete_rows(table=\"customers\", where=\"last_login < 2023-01-01\")\nContext: the customers table has 48,210 rows and no backup was taken today.",
"questions": {
"safe_to_run": {
"type": "noul",
"instructions": "Is this action safe to run without a human approving it first?",
"criteria": {
"true": "Reversible or low-impact, and clearly within the stated task.",
"false": "Destructive, irreversible, or broader than the task requires."
}
}
}
}'
{
"id": "task_01J...",
"model": "jev-1.13",
"answers": {
"safe_to_run": { "type": "noul", "noul": 0.04 }
},
"usage": { "input_tokens": 384, "output_tokens": 22 }
}

0.04 is the model’s likelihood that the answer is yes. Four percent — so the agent stops and asks a human, which is the point of asking.

Request

FieldTypeRequiredDescription
modelstringYesjev-1.13.
statestring | objectYesWhatever your application knows right now. A plain string or a JSON object both work — send the object if that is what you already have.
questionsobjectYesOne or more named questions. The name is yours; it is the key the answer comes back under.

Each question takes a type, an instructions, and — depending on the type — a criteria.

FieldTypeRequiredDescription
typestringYesnoul, choice, or score.
instructionsstring | object | arrayNoWhat you are asking. Write it as you would to a colleague. A choice or a score can be carried by its criteria alone.
criteriasee belowDependsWhat the answer’s values mean.

The three question types

noul — a calibrated likelihood

Use it for a yes/no that deserves a threshold rather than a coin flip. The answer is a number from 0 to 1.

"refund_ok": {
"type": "noul",
"instructions": "Should this refund be approved automatically?",
"criteria": {
"true": "Clear billing error, under $100, first request from this account.",
"false": "Disputed usage, over $100, or a repeat request."
}
}
"refund_ok": { "type": "noul", "noul": 0.83 }

criteria is optional here, but explaining both sides sharpens the answer. Pick your own threshold: auto-approve above 0.9, escalate below 0.6, queue the middle for review.

noul is the type’s real name, not a typo for bool. A misspelled type is rejected with a 400 that names the three valid ones.

choice — one of your named options

criteria is an object mapping each option name to what it means. The answer names the winning option and shows the full distribution.

"route": {
"type": "choice",
"instructions": "Which queue should this ticket go to?",
"criteria": {
"billing": "Payment, invoices, refunds, or charges.",
"technical": "The product is broken or erroring.",
"sales": "Pre-purchase questions about plans or pricing."
}
}
"route": {
"type": "choice",
"choice": "billing",
"probabilities": { "billing": 1, "technical": 0, "sales": 0 },
"confidence": 1
}

score — a position on your scale

criteria is an array describing the scale, lowest first. The answer is a continuous value over the array indices, starting at 0.

"urgency": {
"type": "score",
"instructions": "How urgent is this ticket?",
"criteria": [
"1 - can wait a week",
"2 - answer within a few days",
"3 - answer today",
"4 - answer within an hour",
"5 - wake someone up"
]
}
"urgency": {
"type": "score",
"score": 1.98,
"legend": { "0": "1 - can wait a week", "1": "2 - answer within a few days",
"2": "3 - answer today", "3": "4 - answer within an hour",
"4": "5 - wake someone up" },
"probabilities": { "0": 0, "1": 0.17, "2": 0.67, "3": 0.16, "4": 0 },
"confidence": 0.72
}

1.98 sits between legend["1"] and legend["2"], leaning hard on the latter — “answer today”. Round it when you need a bucket; keep the decimal when you need to sort a queue.

Several questions, one call

Ask everything you need about the same state at once. Each answer is independent, and you are billed for the state once rather than once per question.

curl https://api.beatapi.io/v1/systemone \
-H "Authorization: Bearer $BEATAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "jev-1.13",
"state": {"ticket": "charged twice this month", "plan": "pro", "tenure_days": 412},
"questions": {
"refund_ok": {"type": "noul", "instructions": "Approve the refund automatically?"},
"urgency": {"type": "score", "instructions": "How urgent?", "criteria": ["low", "medium", "high"]}
}
}'

Response

FieldDescription
modelThe model that answered.
answersOne entry per question, keyed by the name you gave it.
usageinput_tokens and output_tokens for this call.
idBeatAPI request identifier — quote it in support tickets. Added on top of the vendor’s three fields; clients that do not model it ignore it.

The response is synchronous: a successful HTTP response means the decision is made. There is nothing to poll and no artifacts to fetch.

Limits

  • Context — 32,000 tokens for state and questions combined.
  • No sampling parameters. temperature, top_p, seed and friends are not accepted; the model is deterministic in shape by design.
  • No streaming. The answer is a value, not a sequence of tokens.
  • A choice needs a non-empty criteria object and a score needs a non-empty criteria array — both are rejected with a 400 that says which. instructions is optional, but an empty one is rejected rather than sent.

Errors

HTTP statusCodeMeaning
400 / 422bad_requestInvalid JSON, model, question type, or criteria.
401 / 403processing_unavailableAuthentication or access cannot be completed.
402insufficient_creditsThe account does not have enough credits.
429rate_limit_exceededThe request limit was reached.
5xxprocessing_unavailableThe request could not be completed.

Failed calls do not consume credits.

Billing

Decisions are billed on input tokens only — the tokens your state and questions occupy — at $0.042 per 1M input tokens. Output is not billed, because the output is a typed value rather than generated prose. usage.input_tokens in every response tells you exactly what the call counted, so you can reconcile spend per call.

Asking several questions about one state in a single call is therefore materially cheaper than sending the same state several times.