API reference, Decisions
Answer typed questions about a state
POST/v1/decideoperation id decide
Answers every question against the shared state in one call. The state is billed once. Requests to an exact
GPU model version (for example dex-1.0.0) or with fallback: "never" are never served by the fallback path;
send fallback: "never" for content moderation, because the fallback's content filters block that content.
A body that repeats a key in any JSON object is rejected with 400 duplicate_key. See docs/spec/dex-v1.md
sections 5 to 7.
Authentication. API key as authorization: Bearer <key>, scope decide.
Parameters
idempotency-keyheader · stringidempotent-replayed: true and the original response: the same id, created, model,
answers and usage, including the original charge fields. The same key with a different body answers 409.1 to 255 characterspattern ^[\x21-\x7E]+$
x-client-request-idheader · stringinvalid_header.1 to 128 characterspattern ^[\x21-\x7E]+$
Request body
application/json, schema DecideRequest
modelModelNamedex-1 or an exact version such as dex-1.0.0.default "dex-1"pattern ^dex-[0-9]+(\.[0-9]+\.[0-9]+)?$
stateStaterequired{{path}} references. A string of only whitespace, an empty object and an empty array are rejected (empty
objects and arrays inside the state are fine). At most 16,384 tokens after rendering.string: 1 to 65,536 characters, pattern \Sobject: at least 1 entriesarray of any: at least 1 items
questionsobjectrequired1 to 32 entrieskeys match ^[A-Za-z][A-Za-z0-9_-]{0,63}$
questions.<question id>Questiontype is pickPickQuestionquestions.<question id>.typestringrequiredalways "pick"
questions.<question id>.instructionsInstructionsrequired{{path}} references to state fields, such as {{ticket.body}}.1 to 4,000 characterspattern \S
questions.<question id>.criteriaCriteria{{path}} references. A string of only whitespace is rejected. null is the same as leaving the field out.string: 1 to 4,000 characters, pattern \Sarray of string: 1 to 20 items
questions.<question id>.optionsobjectrequiredBilling
and billing are the same label). A description of only whitespace is rejected.1 to 255 entrieskeys match ^\S(.*\S)?$, 1 to 100 charactersvalues: string | null, 1 to 1,000 characters, pattern \S
questions.<question id>.options.<label>string | null1 to 1,000 characterspattern \S
questions.<question id>.min_confidenceMinConfidenceabstained set to true.0 to 1
type is rate (beta)RateQuestionquestions.<question id>.typestringrequiredalways "rate"
questions.<question id>.instructionsInstructionsrequired{{path}} references to state fields, such as {{ticket.body}}.1 to 4,000 characterspattern \S
questions.<question id>.criteriaCriteria{{path}} references. A string of only whitespace is rejected. null is the same as leaving the field out.string: 1 to 4,000 characters, pattern \Sarray of string: 1 to 20 items
questions.<question id>.levelsarray of stringrequired2 to 10 itemsunique itemseach item: 1 to 200 characters, pattern ^\S(.*\S)?$
questions.<question id>.min_confidenceMinConfidenceabstained set to true.0 to 1
type is checkCheckQuestionquestions.<question id>.typestringrequiredalways "check"
questions.<question id>.instructionsInstructionsrequired{{path}} references to state fields, such as {{ticket.body}}.1 to 4,000 characterspattern \S
questions.<question id>.criteriaCriteria{{path}} references. A string of only whitespace is rejected. null is the same as leaving the field out.string: 1 to 4,000 characters, pattern \Sarray of string: 1 to 20 items
questions.<question id>.min_confidenceMinConfidenceabstained set to true.0 to 1
fallbackstringnever keeps the request on the GPU path even when the model is an alias. Exact versions never use the fallback. Send never for content moderation: the fallback's content filters block that content.one of "allow", "never"default "allow"
Responses
200All questions answered.
Headers: x-request-id, x-client-request-id, idempotent-replayed, ratelimit-policy, ratelimit
idstringrequiredpattern ^req_[0-9A-HJKMNP-TV-Z]{26}$
objectstringrequiredalways "decision"
createdintegerrequiredmodelstringrequireddex-1.0.0 or dex-fallback-1.0.0.served_byServedByrequiredgpu is our own engine; fallback is the hosted Azure OpenAI path in the EU data zone.one of "gpu", "fallback"
calibrationstringrequiredpattern ^cal-[0-9]{8}-[0-9]+$
answersobjectrequiredanswers.<question id>Answertype is pickPickAnsweranswers.<question id>.typestringrequiredalways "pick"
answers.<question id>.choicestringrequiredanswers.<question id>.probabilitiesobjectrequiredvalues: Probability, 0 to 1
answers.<question id>.probabilities.<label>Probability0 to 1
answers.<question id>.confidenceConfidencerequired0 to 1
answers.<question id>.abstainedbooleanrequiredtype is rate (beta)RateAnsweranswers.<question id>.typestringrequiredalways "rate"
answers.<question id>.ratingnumberrequired0 to 9
answers.<question id>.levelsarray of stringrequiredanswers.<question id>.probabilitiesarray of Probabilityrequiredlevels, summing to exactly 1.each item: 0 to 1
answers.<question id>.confidenceConfidencerequired0 to 1
answers.<question id>.abstainedbooleanrequiredtype is checkCheckAnsweranswers.<question id>.typestringrequiredalways "check"
answers.<question id>.probabilityProbabilityrequired0 to 1
answers.<question id>.confidenceConfidencerequired0 to 1
answers.<question id>.abstainedbooleanrequiredusageUsagerequiredusage.input_tokensintegerrequiredat least 1
usage.state_tokensintegerrequiredat least 1
usage.question_tokensintegerrequiredat least 1
usage.charge_micro_centsintegerrequiredformat int64at least 0
usage.unit_price_micro_centsintegerrequiredat least 0
usage.tierstringrequiredone of "t1", "t2", "t3", "test"
400The request could not be read. Codes: invalid_json (not JSON, not UTF-8, or a \u escape that is half of a
surrogate pair, such as "\ud800" without its low half), duplicate_key (a JSON object
repeats a key; param is the JSON pointer of the repeated member, such as /questions/q1/options/a),
unsupported_media_type (not application/json) and invalid_header (a malformed idempotency-key or
x-client-request-id; param names the header).
Headers: x-request-id, x-client-request-id
Error envelope. See Errors for every type and code.
401Missing, unknown, revoked or expired credentials.
Headers: x-request-id, x-client-request-id
Error envelope. See Errors for every type and code.
402The balance does not cover this request's charge. Nothing was charged.
Headers: x-request-id, x-client-request-id
Error envelope. See Errors for every type and code.
403The credentials are valid but not allowed to do this (missing scope, suspended account or failed CSRF check).
Headers: x-request-id, x-client-request-id
Error envelope. See Errors for every type and code.
404The model, key or route does not exist, or the model version is retired.
Headers: x-request-id, x-client-request-id
Error envelope. See Errors for every type and code.
409The idempotency key was used with a different body, or the first request with this key is still running.
Headers: x-request-id, x-client-request-id, retry-after
Error envelope. See Errors for every type and code.
413The body or its token counts exceed the limits.
Headers: x-request-id, x-client-request-id
Error envelope. See Errors for every type and code.
422The request is well-formed JSON but breaks a validation rule. param names the field as a dotted path.
Codes: unknown_field, missing_field, invalid_type (wrong JSON type), invalid_value (right type, value
out of range: an empty or over-long string, a state, instructions, criteria string or option description
of only whitespace, an empty questions or options object, an empty state object or array, a pattern or
allowed-value mismatch such as a label or level with outer whitespace, a state nested deeper than 32 levels, a
bad date or date range), field_not_allowed (options or levels on the wrong question type),
invalid_question_id, too_many_questions, too_many_options, invalid_levels (including two levels equal
after Unicode NFC normalisation and lower-casing), invalid_min_confidence, duplicate_label (two option
labels equal after Unicode NFC normalisation and lower-casing, such as Billing and billing),
state_path_not_found, state_not_json and, on the console, top_up_limit_exceeded.
Headers: x-request-id, x-client-request-id
Error envelope. See Errors for every type and code.
429A rate limit or quota is exhausted. Retry after the given number of seconds. For test_daily_quota that is
the time until 00:00 UTC; clients should not retry it automatically.
Headers: x-request-id, x-client-request-id, retry-after, ratelimit-policy, ratelimit
Error envelope. See Errors for every type and code.
500An unexpected error. Any charge was refunded. Retry with the same idempotency key.
Headers: x-request-id, x-client-request-id
Error envelope. See Errors for every type and code.
503No serving path could answer in time, or a dependency is down. Any charge was refunded.
Headers: x-request-id, x-client-request-id, retry-after
Error envelope. See Errors for every type and code.
Example request
curl -X POST "https://api.thinqit.ai/v1/decide" \
-H "authorization: Bearer $DEX_API_KEY" \
-H "content-type: application/json" \
--data @- <<'JSON'
{
"state": {
"ticket": {
"channel": "email",
"subject": "Charged twice, still no answer",
"body": "This is the third time I am writing. You charged my card twice for order 4471 and nobody replies. Fix it today or I cancel my subscription."
}
},
"questions": {
"team": {
"type": "pick",
"instructions": "Which team should handle {{ticket.body}}?",
"options": {
"billing": "Payments, refunds and invoices",
"technical": "Bugs, outages and sign-in problems",
"shipping": "Delivery and returns",
"other": null
},
"min_confidence": 0.5
},
"urgency": {
"type": "rate",
"instructions": "How fast must we reply to {{ticket.body}}?",
"levels": [
"Can wait",
"This week",
"Within two working days",
"Today"
]
},
"angry": {
"type": "check",
"instructions": "Is the writer of {{ticket.body}} angry?",
"min_confidence": 0.6
}
}
}
JSONExamples
Illustrative values. Examples show the shape of requests and responses; the numbers in them are not measured results.
Request: The Quickstart's support ticket (pick, rate and check); the mock server answers it by default
{
"state": {
"ticket": {
"channel": "email",
"subject": "Charged twice, still no answer",
"body": "This is the third time I am writing. You charged my card twice for order 4471 and nobody replies. Fix it today or I cancel my subscription."
}
},
"questions": {
"team": {
"type": "pick",
"instructions": "Which team should handle {{ticket.body}}?",
"options": {
"billing": "Payments, refunds and invoices",
"technical": "Bugs, outages and sign-in problems",
"shipping": "Delivery and returns",
"other": null
},
"min_confidence": 0.5
},
"urgency": {
"type": "rate",
"instructions": "How fast must we reply to {{ticket.body}}?",
"levels": [
"Can wait",
"This week",
"Within two working days",
"Today"
]
},
"angry": {
"type": "check",
"instructions": "Is the writer of {{ticket.body}} angry?",
"min_confidence": 0.6
}
}
}Request: Dutch customer support triage (pick, rate and check on one ticket)
{
"model": "dex-1",
"state": {
"bericht": {
"kanaal": "webformulier",
"onderwerp": "Accu laadt niet meer op",
"tekst": "Sinds de software-update van vorige week laadt de accu van mijn e-bike niet meer op. Ik heb de fiets pas drie maanden en heb hem elke dag nodig voor mijn werk. Kan ik hem omruilen of komt er een monteur langs?"
},
"klant": {
"klant_sinds": "2026-06-14",
"bestellingen": 2
}
},
"questions": {
"afdeling": {
"type": "pick",
"instructions": "Welke afdeling moet {{bericht.tekst}} oppakken?",
"options": {
"garantie": "Omruilen of terugbetalen binnen de garantietermijn",
"reparatie": "Defecten, onderhoud en monteur aan huis",
"bezorging": "Levering en track-and-trace",
"overig": null
},
"min_confidence": 0.5
},
"spoed": {
"type": "rate",
"instructions": "Hoe snel moeten we reageren op {{bericht.tekst}}?",
"levels": [
"Kan wachten",
"Binnen een week",
"Binnen twee werkdagen",
"Vandaag"
]
},
"wil_omruilen": {
"type": "check",
"instructions": "Vraagt de klant in {{bericht.tekst}} om omruilen of vervanging?",
"min_confidence": 0.6
}
}
}Request: English content moderation pinned to an exact version, never using the fallback
{
"model": "dex-1.0.0",
"fallback": "never",
"state": {
"post": {
"board": "Bikes for sale",
"title": "Selling my old road bike",
"text": "Still available, cash only. Text me on +31 6 1234 5678. The next person who lowballs me will find their home address posted in this thread."
}
},
"questions": {
"rule": {
"type": "pick",
"instructions": "Which community rule does {{post.text}} break, if any?",
"criteria": [
"A threat to expose where someone lives counts as harassment even when it is conditional.",
"Sharing your own phone number in a sale listing is allowed."
],
"options": {
"none": "Breaks no rule",
"harassment": "Threats, intimidation or exposing someone's personal details",
"spam": "Repeated or unsolicited promotion",
"self_harm": "Encourages or describes self-harm",
"hate": "Attacks people for a protected characteristic"
},
"min_confidence": 0.6
},
"severity": {
"type": "rate",
"instructions": "How severe is the worst problem in {{post.text}}?",
"levels": [
"None",
"Mild",
"Serious",
"Severe"
]
},
"has_phone_number": {
"type": "check",
"instructions": "Does {{post.text}} contain a phone number?"
}
}
}Response: Billing, reply today, and the writer is angry (the Quickstart's illustrative answer)
{
"id": "req_01M5D0000000000000000000AB",
"object": "decision",
"created": 1792497700,
"model": "dex-1.0.0",
"served_by": "gpu",
"calibration": "cal-20261015-1",
"answers": {
"team": {
"type": "pick",
"choice": "billing",
"probabilities": {
"billing": 0.9112,
"technical": 0.0301,
"shipping": 0.0204,
"other": 0.0383
},
"confidence": 0.8729,
"abstained": false
},
"urgency": {
"type": "rate",
"rating": 2.699,
"levels": [
"Can wait",
"This week",
"Within two working days",
"Today"
],
"probabilities": [
0.012,
0.061,
0.143,
0.784
],
"confidence": 0.5761,
"abstained": false
},
"angry": {
"type": "check",
"probability": 0.9421,
"confidence": 0.8842,
"abstained": false
}
},
"usage": {
"input_tokens": 118,
"state_tokens": 52,
"question_tokens": 66,
"charge_micro_cents": 590,
"unit_price_micro_cents": 5,
"tier": "t1"
}
}Response: The team choice abstains; urgency and the exchange request are clear
{
"id": "req_01M5CJXHG0M9S346Q3D25VT4F5",
"object": "decision",
"created": 1792497600,
"model": "dex-1.0.0",
"served_by": "gpu",
"calibration": "cal-20261015-1",
"answers": {
"afdeling": {
"type": "pick",
"choice": "garantie",
"probabilities": {
"garantie": 0.6423,
"reparatie": 0.3287,
"bezorging": 0.0102,
"overig": 0.0188
},
"confidence": 0.3136,
"abstained": true
},
"spoed": {
"type": "rate",
"rating": 2.2957,
"levels": [
"Kan wachten",
"Binnen een week",
"Binnen twee werkdagen",
"Vandaag"
],
"probabilities": [
0.0195,
0.1067,
0.4325,
0.4413
],
"confidence": 0.5108,
"abstained": false
},
"wil_omruilen": {
"type": "check",
"probability": 0.8732,
"confidence": 0.7465,
"abstained": false
}
},
"usage": {
"input_tokens": 273,
"state_tokens": 142,
"question_tokens": 131,
"charge_micro_cents": 1365,
"unit_price_micro_cents": 5,
"tier": "t1"
}
}Response: Harassment with high confidence, rated serious
{
"id": "req_01M5CJXPP9V37E3S3E28JT97KB",
"object": "decision",
"created": 1792497605,
"model": "dex-1.0.0",
"served_by": "gpu",
"calibration": "cal-20261015-1",
"answers": {
"rule": {
"type": "pick",
"choice": "harassment",
"probabilities": {
"none": 0.0978,
"harassment": 0.8822,
"spam": 0.0132,
"self_harm": 0.0024,
"hate": 0.0044
},
"confidence": 0.7844,
"abstained": false
},
"severity": {
"type": "rate",
"rating": 2.0805,
"levels": [
"None",
"Mild",
"Serious",
"Severe"
],
"probabilities": [
0.0089,
0.1194,
0.654,
0.2177
],
"confidence": 0.5966,
"abstained": false
},
"has_phone_number": {
"type": "check",
"probability": 0.9548,
"confidence": 0.9096,
"abstained": false
}
},
"usage": {
"input_tokens": 262,
"state_tokens": 61,
"question_tokens": 201,
"charge_micro_cents": 1310,
"unit_price_micro_cents": 5,
"tier": "t1"
}
}