API reference
Send a document and typed questions. Every answer comes back with a calibrated probability for each option.
Base URL https://api.decide.sh
Quickstart
Create a key in the console and export it as DECIDE_API_KEY. Then ask two questions about a support ticket:
curl https://api.decide.sh/v1/decide \
-H "Authorization: Bearer $DECIDE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "decide-1.1",
"state": "Hi, I was charged twice for my October invoice and the second charge is still pending. Can someone reverse it before it posts?",
"questions": {
"team": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": {
"Billing": "charges, refunds, invoices",
"Account": "sign-in, profile, access",
"Technical": "bugs, errors, outages"
}
},
"urgent": {
"type": "noul",
"instructions": "Does it need an answer today?"
}
}
}'{
"model": "decide-1.1",
"answers": {
"team": {
"type": "choice",
"choice": "Billing",
"confidence": 0.97,
"probabilities": { "Billing": 0.97, "Account": 0.02, "Technical": 0.01 }
},
"urgent": { "type": "noul", "noul": 0.81 }
},
"usage": { "input_tokens": 164, "output_tokens": 0 }
}Each answer sits under its question’s name. team carries a probability for every team, and urgent the probability of yes.
Authentication
Every request carries a key in the Authorization header: Authorization: Bearer dsk_…
Keys are shown once, when you create them; we store only a hash. Use them from your servers, never from a browser or an app. A revoked key stops working within a minute. Keys answer once the account has a payment method; until then, requests return 402.
Models
Every request names its model. decide-1.1 is our Smart model: ready out of the box, it reads text and JSON. Fast, Edge and Smart models can also be trained on your data in the console; each trained model gets its own id (mdl_…) and an eval report. Call it by that id with any of your account’s keys; it answers your account alone. Calls to Fast models are free. During the beta, trained models scale to zero when idle, so the first call after a quiet spell can take a few seconds; decide-1.1 is always warm.
Under 200 examples, an Edge or Smart run may not improve on the model it starts from. A finished run is billed either way, with no refunds.
GET/v1/models
Lists the models your key can call: decide-1.1 and your account’s trained models.
curl https://api.decide.sh/v1/models \
-H "Authorization: Bearer $DECIDE_API_KEY"{
"object": "list",
"data": [{ "id": "decide-1.1", "object": "model" }]
}Make a decision
POST/v1/decide
Answers every question about one document in one request. Also served at /v1/systemone, with the same body.
Request body
| Field | Type | Description |
|---|---|---|
modelrequired | string | The model that answers, such as decide-1.1. |
staterequired | string or object | The document. An object is read as its JSON text. Up to 50,000 characters. |
questionsrequired | object | Up to 64 named questions. Each answer comes back under its question’s name. |
assets | object | Images and PDFs the state refers to. See Images and PDFs. |
min_confidence | number or object | A threshold each answer is marked against. See Thresholds. |
noul_floor | boolean | Whether unsure yes/no answers are reported at 0.801 or 0.199. Defaults to true on decide-1.1, and on your trained models to the model’s own setting, off unless you turn it on. See Question types. |
allow_unknown | boolean | Adds unknown_probability to every answer. See Optional fields. |
graded_confidence | boolean | Adds graded_p_yes, the calibrated probability of yes, to yes/no answers on decide-1.1 and to any answer the floor changed. |
A question
| Field | Type | Description |
|---|---|---|
typerequired | string | choice, score or noul (yes/no). |
instructionsrequired | string | The question, in your words. |
criteria | array or object | The options. Required for choice and score; optional for noul. |
Response
| Field | Type | Description |
|---|---|---|
model | string | The model that answered. |
answers | object | One answer per question, under the same name. See Question types. |
usage | object | input_tokens, the tokens billed, and output_tokens, never billed. |
request_id | string | On your trained models: the request’s id, the same as x-request-id. |
Response headers
| Field | Type | Description |
|---|---|---|
x-request-id | string | Names the request. Quote it when you write to us. |
x-inference-time-ms | number | Time on the model server, in milliseconds. server-timing carries it as inference, followed by the API’s own timings. |
Question types
Three types, each answered with its full distribution. Probabilities are rounded to four decimal places.
choice: one label from a set
criteria is an array of labels, or an object from each label to what it means. Up to 100 options.
"team": {
"type": "choice",
"instructions": "Which team should handle this?",
"criteria": ["Billing", "Account", "Technical"]
}"team": {
"type": "choice",
"choice": "Billing",
"confidence": 0.97,
"probabilities": { "Billing": 0.97, "Account": 0.02, "Technical": 0.01 }
}choice is the most likely label. probabilities covers every label and sums to 1. confidence is how sure the model is of its answer, from 0 to 1.
score: a level on your scale
criteria is an array of level descriptions, lowest first. Up to 32 levels, each with a description.
"frustration": {
"type": "score",
"instructions": "How frustrated is the customer?",
"criteria": ["Calm", "Mildly annoyed", "Frustrated", "Angry"]
}"frustration": {
"type": "score",
"score": 1.76,
"confidence": 0.58,
"probabilities": { "0": 0.04, "1": 0.27, "2": 0.58, "3": 0.11 },
"legend": { "0": "Calm", "1": "Mildly annoyed", "2": "Frustrated", "3": "Angry" }
}score is the expected level: the level indices, from 0, weighted by their probabilities. Here 1.76 sits between Mildly annoyed and Frustrated. probabilities and legend are keyed by index.
noul: yes or no
criteria is optional: an object with what true and false mean.
"urgent": {
"type": "noul",
"instructions": "Does it need an answer today?",
"criteria": {
"true": "the customer is blocked or losing money",
"false": "it can wait until tomorrow"
}
}"urgent": { "type": "noul", "noul": 0.81 }noul is the probability of yes. On decide-1.1, by default, an unsure answer is reported at 0.801 on the side the model leans to, or 0.199 for no, so no answer sits between 0.2 and 0.8. A confident answer is reported as it is. The floor never changes which answer is given, and choice and score answers are untouched. Your trained models report the calibrated probability by default: of answers given 0.9, about nine in ten are yes on held-out data.
Setting the floor
noul_floor sets it for one request. On decide-1.1, "noul_floor": false is routing mode: noul is the raw calibrated probability, for systems that treat unsure answers differently from sure ones. Routing mode isn’t for benchmark or leaderboard runs. On your trained models, "noul_floor": true turns the floor on for one request; to floor a model by default, switch it on in the model’s page in the console. A request’s setting wins over the model’s. Wherever noul may be floored, graded_confidence adds the calibrated probability as graded_p_yes.
Images and PDFs
Models trained on scans read images and PDFs; decide-1.1 reads text. Put a reference in the state where the file belongs, and send the file under that reference in assets.
{
"model": "mdl_…",
"state": {
"note": "Supplier invoice, uploaded 14 October",
"invoice": "invoice-1"
},
"assets": {
"invoice-1": { "format": "pdf", "data_base64": "JVBERi0xLjcK…" }
},
"questions": {
"paid": { "type": "noul", "instructions": "Is the invoice stamped paid?" }
}
}| Field | Type | Description |
|---|---|---|
formatrequired | string | png, jpg, jpeg or pdf. |
data_base64required | string | The file, base64-encoded. |
- Every reference appears in the state, and every asset is referenced.
- Up to 16 assets per request, inside the 6 MB request limit.
- A PDF contributes its first 4 pages as images, plus its text layer. A scan with no text layer is read as images alone.
- In a batch,
assetsis shared by every state. - A model that reads text answers
422to a request with assets.
Thresholds
Set min_confidence and every answer is marked against it. Nothing is removed: a marked answer keeps every field.
{
"model": "decide-1.1",
"state": "…",
"questions": { "team": { … }, "urgent": { … } },
"min_confidence": { "choice:3-5": 0.9, "noul:2": 0.75, "default": 0.8 }
}"team": {
"type": "choice",
"choice": "Billing",
"confidence": 0.97,
"probabilities": { "Billing": 0.97, "Account": 0.02, "Technical": 0.01 },
"abstention": "passed",
"abstention_threshold": 0.9
},
"urgent": {
"type": "noul",
"noul": 0.801,
"abstention": "abstained",
"abstention_threshold": 0.75,
"low_confidence": true
}| Field | Type | Description |
|---|---|---|
abstention | string | passed at or above the threshold, abstained below it, unevaluated when the answer carries no confidence to compare. |
abstention_threshold | number | The threshold the answer was held to. |
low_confidence | boolean | true on an abstained answer. |
One threshold, or one per option count
A number from 0 to 1 holds every answer to the same threshold. An object sets one per option-count bucket: choice:2, choice:3-5, choice:6-10 and choice:11+, the same four for score, and noul:2. default covers any bucket the object doesn’t name; without it, those answers aren’t held to a threshold.
Yes/no answers are held to the calibrated probability, not the floored noul. Each model’s eval report lists the error rate at each threshold.
Optional fields
allow_unknown
With "allow_unknown": true, every answer adds unknown_probability: the probability that the document doesn’t settle the question. The other fields don’t change, and probabilities still sums to 1 over your options. It adds about 13 input tokens per question.
graded_confidence
With "graded_confidence": true, yes/no answers on decide-1.1, and any answer the floor changed, add graded_p_yes: the calibrated probability of yes, beside the reported noul. An answer whose noul is already calibrated carries nothing extra. Nothing else changes, and it costs nothing.
"urgent": { "type": "noul", "noul": 0.801, "graded_p_yes": 0.62 }Batches
POST/v1/decide/batch
The same questions about up to 64 documents. Send states, an array, in place of state; every other field works as in a single decision.
{
"model": "decide-1.1",
"states": ["first ticket …", "second ticket …"],
"questions": {
"urgent": { "type": "noul", "instructions": "Does it need an answer today?" }
}
}{
"model": "decide-1.1",
"results": [
{
"model": "decide-1.1",
"answers": { "urgent": { "type": "noul", "noul": 0.91 } },
"usage": { "input_tokens": 41, "output_tokens": 0 }
},
{
"model": "decide-1.1",
"answers": { "urgent": { "type": "noul", "noul": 0.07 } },
"usage": { "input_tokens": 38, "output_tokens": 0 }
}
],
"total_usage": { "input_tokens": 79, "output_tokens": 0 }
}results holds one result per document, in order. total_usage adds up their usage.
Usage and billing
usage.input_tokens is what you’re billed for: $0.03 per million on decide-1.1 and your Smart models, and $0.02 on your Edge models. The document counts once per request, and each question adds its own tokens, so a second question about the same document costs only its own words. Output is free: usage.output_tokens is never billed. Calls to yourFast models are free: the response still reports usage.input_tokens, and none of it is billed.
Only answered requests are billed. Usage is metered every 15 minutes and billed monthly by card; the console shows the month so far. Examples are on pricing.
Errors
Errors are JSON. error names the kind and message says what to change; error_type and detail repeat them for clients that read those names.
{
"error": "not_found_error",
"error_type": "not_found_error",
"message": "There is no model \"decide-2\". Available: decide-1.1.",
"detail": "There is no model \"decide-2\". Available: decide-1.1."
}| Status | error | Means |
|---|---|---|
| 400 | invalid_request_error | The body isn’t a JSON object, state or questions is missing, or the assets don’t match the state. |
| 401 | authentication_error | No key, a malformed key, or a revoked one. |
| 402 | payment_required | The account has no payment method yet. |
| 404 | not_found_error | No such model or path. The message lists the models. |
| 405 | invalid_request_error | The wrong method: decisions are POST, the model list is GET. |
| 413 | request_too_large | Over a limit. |
| 422 | invalid_request_error | A field can’t be read: a question with no instructions, a score level with no description, a threshold outside 0 to 1, or assets sent to a model that reads text. |
| 500, 502 | api_error | The model failed on this request. Retry once; if it fails again, send us the request id. |
| 503 | service_unavailable, overloaded_error | Busy, briefly unavailable, or no answer within 25 seconds. Retry with backoff. |
Failed requests aren’t billed.
Limits
| Request body | 6 MB, images included |
| Document | 50,000 characters |
| Questions | 64 per request |
| Documents per batch | 64 |
| Options | 100 per choice, 32 levels per score, 512 across a request’s questions |
| Assets | 16 per request; a PDF contributes its first 4 pages |
| Time | 25 seconds, then 503 |
| Rate | No per-key limit. Under load, requests return 503. |