Sign-up and API keys are open. Paid top-ups open soon.What changed
Docs menu

API reference, Decisions

Answer typed questions about a state

View as Markdown

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 · string
Makes retries safe. Within 24 hours, a repeat with the same key and the same body is never charged again. It answers with idempotent-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 characters
  • pattern ^[\x21-\x7E]+$
x-client-request-idheader · string
Your own correlation id, accepted on every public operation. Logged with the request metadata and echoed in the response. A malformed value answers 400 invalid_header.
  • 1 to 128 characters
  • pattern ^[\x21-\x7E]+$

Request body

application/json, schema DecideRequest

modelModelName
An alias such as dex-1 or an exact version such as dex-1.0.0.
  • default "dex-1"
  • pattern ^dex-[0-9]+(\.[0-9]+\.[0-9]+)?$
stateStaterequired
The material the questions are about. A string, or JSON whose fields questions can point at with {{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 \S
  • object: at least 1 entries
  • array of any: at least 1 items
questionsobjectrequired
Question id to question. Ids are for your code only and never reach the model.
  • 1 to 32 entries
  • keys match ^[A-Za-z][A-Za-z0-9_-]{0,63}$
questions.<question id>Question
When type is pickPickQuestion
questions.<question id>.typestringrequired
  • always "pick"
questions.<question id>.instructionsInstructionsrequired
What to decide; a string of only whitespace is rejected. May contain {{path}} references to state fields, such as {{ticket.body}}.
  • 1 to 4,000 characters
  • pattern \S
questions.<question id>.criteriaCriteria
Extra rules or definitions the model must apply. May contain {{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 \S
  • array of string: 1 to 20 items
questions.<question id>.optionsobjectrequired
Label to description (or null). Key order is kept and shown to the model in that order. Labels have no leading or trailing whitespace and are unique after Unicode NFC normalisation and lower-casing (Billing and billing are the same label). A description of only whitespace is rejected.
  • 1 to 255 entries
  • keys match ^\S(.*\S)?$, 1 to 100 characters
  • values: string | null, 1 to 1,000 characters, pattern \S
questions.<question id>.options.<label>string | null
  • 1 to 1,000 characters
  • pattern \S
questions.<question id>.min_confidenceMinConfidence
When the answer's confidence is below this value, the answer carries abstained set to true.
  • 0 to 1
When type is rate (beta)RateQuestion
questions.<question id>.typestringrequired
  • always "rate"
questions.<question id>.instructionsInstructionsrequired
What to decide; a string of only whitespace is rejected. May contain {{path}} references to state fields, such as {{ticket.body}}.
  • 1 to 4,000 characters
  • pattern \S
questions.<question id>.criteriaCriteria
Extra rules or definitions the model must apply. May contain {{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 \S
  • array of string: 1 to 20 items
questions.<question id>.levelsarray of stringrequired
Ordered from lowest (index 0) to highest. A level follows the label rule (no leading or trailing whitespace), and levels are unique after Unicode NFC normalisation and lower-casing.
  • 2 to 10 items
  • unique items
  • each item: 1 to 200 characters, pattern ^\S(.*\S)?$
questions.<question id>.min_confidenceMinConfidence
When the answer's confidence is below this value, the answer carries abstained set to true.
  • 0 to 1
When type is checkCheckQuestion
questions.<question id>.typestringrequired
  • always "check"
questions.<question id>.instructionsInstructionsrequired
What to decide; a string of only whitespace is rejected. May contain {{path}} references to state fields, such as {{ticket.body}}.
  • 1 to 4,000 characters
  • pattern \S
questions.<question id>.criteriaCriteria
Extra rules or definitions the model must apply. May contain {{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 \S
  • array of string: 1 to 20 items
questions.<question id>.min_confidenceMinConfidence
When the answer's confidence is below this value, the answer carries abstained set to true.
  • 0 to 1
fallbackstring
never 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

idstringrequired
  • pattern ^req_[0-9A-HJKMNP-TV-Z]{26}$
objectstringrequired
  • always "decision"
createdintegerrequired
Unix time in seconds.
modelstringrequired
The exact version that answered, such as dex-1.0.0 or dex-fallback-1.0.0.
served_byServedByrequired
gpu is our own engine; fallback is the hosted Azure OpenAI path in the EU data zone.
  • one of "gpu", "fallback"
calibrationstringrequired
Calibration version applied; its public report shows the reliability curves.
  • pattern ^cal-[0-9]{8}-[0-9]+$
answersobjectrequired
Question id to answer, in the request's question order.
answers.<question id>Answer
When type is pickPickAnswer
answers.<question id>.typestringrequired
  • always "pick"
answers.<question id>.choicestringrequired
The most probable label. Ties go to the earlier option.
answers.<question id>.probabilitiesobjectrequired
Label to probability, in option order, summing to exactly 1.
  • values: Probability, 0 to 1
answers.<question id>.probabilities.<label>Probability
Rounded to 4 decimals.
  • 0 to 1
answers.<question id>.confidenceConfidencerequired
How concentrated the distribution is (see the spec, section 6.3). Not a correctness guarantee.
  • 0 to 1
answers.<question id>.abstainedbooleanrequired
When type is rate (beta)RateAnswer
answers.<question id>.typestringrequired
  • always "rate"
answers.<question id>.ratingnumberrequired
Probability-weighted level index, from 0 to the number of levels minus 1.
  • 0 to 9
answers.<question id>.levelsarray of stringrequired
The levels you sent, in order.
answers.<question id>.probabilitiesarray of Probabilityrequired
Aligned with levels, summing to exactly 1.
  • each item: 0 to 1
answers.<question id>.confidenceConfidencerequired
How concentrated the distribution is (see the spec, section 6.3). Not a correctness guarantee.
  • 0 to 1
answers.<question id>.abstainedbooleanrequired
When type is checkCheckAnswer
answers.<question id>.typestringrequired
  • always "check"
answers.<question id>.probabilityProbabilityrequired
Rounded to 4 decimals.
  • 0 to 1
answers.<question id>.confidenceConfidencerequired
How concentrated the distribution is (see the spec, section 6.3). Not a correctness guarantee.
  • 0 to 1
answers.<question id>.abstainedbooleanrequired
usageUsagerequired
usage.input_tokensintegerrequired
Billed tokens, equal to state_tokens plus question_tokens. Output is free.
  • at least 1
usage.state_tokensintegerrequired
  • at least 1
usage.question_tokensintegerrequired
  • at least 1
usage.charge_micro_centsintegerrequired
Debited from the balance. 1 EUR is 100,000,000 micro-cents.
  • format int64
  • at least 0
usage.unit_price_micro_centsintegerrequired
Price per token for this request's tier (5 is EUR 0.05 per million tokens).
  • at least 0
usage.tierstringrequired
  • one 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
    }
  }
}
JSON

Examples

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"
  }
}