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?"
      }
    }
  }'
Response · synthetic sample
{
  "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.

Request
curl https://api.decide.sh/v1/models \
  -H "Authorization: Bearer $DECIDE_API_KEY"
Response
{
  "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

FieldTypeDescription
modelrequiredstringThe model that answers, such as decide-1.1.
staterequiredstring or objectThe document. An object is read as its JSON text. Up to 50,000 characters.
questionsrequiredobjectUp to 64 named questions. Each answer comes back under its question’s name.
assetsobjectImages and PDFs the state refers to. See Images and PDFs.
min_confidencenumber or objectA threshold each answer is marked against. See Thresholds.
noul_floorbooleanWhether 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_unknownbooleanAdds unknown_probability to every answer. See Optional fields.
graded_confidencebooleanAdds 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

FieldTypeDescription
typerequiredstringchoice, score or noul (yes/no).
instructionsrequiredstringThe question, in your words.
criteriaarray or objectThe options. Required for choice and score; optional for noul.

Response

FieldTypeDescription
modelstringThe model that answered.
answersobjectOne answer per question, under the same name. See Question types.
usageobjectinput_tokens, the tokens billed, and output_tokens, never billed.
request_idstringOn your trained models: the request’s id, the same as x-request-id.

Response headers

FieldTypeDescription
x-request-idstringNames the request. Quote it when you write to us.
x-inference-time-msnumberTime 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.

Question
"team": {
  "type": "choice",
  "instructions": "Which team should handle this?",
  "criteria": ["Billing", "Account", "Technical"]
}
Answer
"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.

Question
"frustration": {
  "type": "score",
  "instructions": "How frustrated is the customer?",
  "criteria": ["Calm", "Mildly annoyed", "Frustrated", "Angry"]
}
Answer
"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.

Question
"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"
  }
}
Answer
"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.

Request
{
  "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?" }
  }
}
FieldTypeDescription
formatrequiredstringpng, jpg, jpeg or pdf.
data_base64requiredstringThe 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, assets is shared by every state.
  • A model that reads text answers 422 to a request with assets.

Thresholds

Set min_confidence and every answer is marked against it. Nothing is removed: a marked answer keeps every field.

Request
{
  "model": "decide-1.1",
  "state": "…",
  "questions": { "team": { … }, "urgent": { … } },
  "min_confidence": { "choice:3-5": 0.9, "noul:2": 0.75, "default": 0.8 }
}
Answers
"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
}
FieldTypeDescription
abstentionstringpassed at or above the threshold, abstained below it, unevaluated when the answer carries no confidence to compare.
abstention_thresholdnumberThe threshold the answer was held to.
low_confidencebooleantrue 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.

Answer
"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.

Request
{
  "model": "decide-1.1",
  "states": ["first ticket …", "second ticket …"],
  "questions": {
    "urgent": { "type": "noul", "instructions": "Does it need an answer today?" }
  }
}
Response · synthetic sample
{
  "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.

404
{
  "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."
}
StatuserrorMeans
400invalid_request_errorThe body isn’t a JSON object, state or questions is missing, or the assets don’t match the state.
401authentication_errorNo key, a malformed key, or a revoked one.
402payment_requiredThe account has no payment method yet.
404not_found_errorNo such model or path. The message lists the models.
405invalid_request_errorThe wrong method: decisions are POST, the model list is GET.
413request_too_largeOver a limit.
422invalid_request_errorA 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, 502api_errorThe model failed on this request. Retry once; if it fails again, send us the request id.
503service_unavailable, overloaded_errorBusy, briefly unavailable, or no answer within 25 seconds. Retry with backoff.

Failed requests aren’t billed.

Limits

Request body6 MB, images included
Document50,000 characters
Questions64 per request
Documents per batch64
Options100 per choice, 32 levels per score, 512 across a request’s questions
Assets16 per request; a PDF contributes its first 4 pages
Time25 seconds, then 503
RateNo per-key limit. Under load, requests return 503.