Getting started
Quickstart
Get a key, try the API against a local mock today, make a call in curl, Python or TypeScript, and act on the answer.
This page takes you from nothing to a working decision about a support ticket: which team should handle it, how fast to reply, and whether the writer is angry.
Sign-up and API keys are open: sign in to the console with your email address and create a free test key. Paid top-ups open soon. The SDK packages are coming soon to PyPI and npm; until then, install them from Downloads. You can also try everything against a local mock server first.
Get an API key
Keys come from the console at https://thinqit.ai/console/. Sign in with your email address; the first sign-in creates your account. There are two kinds:
- Live keys start with
dex_live_. Calls are billed against your prepaid balance. - Test keys start with
dex_test_. Calls are free up to 250,000 tokens a day per account. Test calls always run on the GPU path and have lower rate limits.
The console shows a key's secret once, when you create it. Store it in an environment variable, where the SDKs and the examples below look for it:
export DEX_API_KEY="<your key>"
Check your key
Before your first decision, confirm the key works and see which account it belongs to. GET /v1/key works with any valid key and never returns the secret.
curl https://api.thinqit.ai/v1/key \
-H "authorization: Bearer $DEX_API_KEY"from thinqit_dex import Client
key = Client().get_key()
print(key.prefix, key.mode, key.scopes)import { Client } from "@thinqit/dex";
const key = await new Client().getKey();
console.log(key.prefix, key.mode, key.scopes);dex whoamiYou get the key's id, its display prefix (such as dex_test_R4tv), live or test, its scopes and the account id. See Authentication and API keys.
Write the request
A request has a state (the material to decide about) and questions (what you want to know about it). Save this as request.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
}
}
}
What each part does:
stateis JSON, so each question can point at one field with{{ticket.body}}. See Field references.teamis apick: choose one option.otherhas no description because its label says enough.urgencyis arate: place the ticket on four levels, listed from lowest to highest.rateis in beta; see Questions.angryis acheck: the probability that the statement is true.min_confidencetells an unsure answer to abstain instead of guessing. See Abstention.- The ids
team,urgencyandangryare yours. The model never sees them.
The request has no model field, so it uses the default alias dex-1.
Autocompletion in your editor
A JSON schema for request bodies is published at https://thinqit.ai/schemas/dex-request.schema.json. Point your editor at it to get completion, hover docs and errors for unknown or misspelled fields while you write a request file. In a file you run with dex decide --file or with the VS Code extension, add a $schema line at the top:
{
"$schema": "https://thinqit.ai/schemas/dex-request.schema.json",
"state": { "ticket": { "body": "..." } },
"questions": {}
}
The CLI and the VS Code extension remove $schema before they send the request, and the extension applies the schema to every *.dex.json file by itself. When you send a body yourself, with curl or an SDK, leave $schema out: the API rejects fields it does not know with 422 unknown_field.
Try it today with a mock server
Prism is an open source mock server. It reads the published API contract, checks each request against it, and answers with the contract's example responses. You need Node.js.
Start the mock:
npx @stoplight/prism-cli mock https://thinqit.ai/openapi.yaml
Prism listens on http://127.0.0.1:4010. In a second terminal, send the request:
curl http://127.0.0.1:4010/v1/decide \
-H "authorization: Bearer dex_test_mock" \
-H "content-type: application/json" \
-d @request.json
What to expect from the mock:
- A valid request gets
200and the contract's first example: the answer to this page's ticket (teamisbilling), the same values as in Read the answer below. The mock does not run a model, so it gives these answers whatever you send. Use it to test your request shape and your response handling. - The other examples. Add
-H "prefer: example=dutchSupport"for the Dutch e-bike ticket from Abstention, or-H "prefer: example=englishModeration"for the English moderation example. - A request that breaks the contract gets a
4xx. Thesl-violationsresponse header lists what is wrong (add-ito curl to see headers). The body is the contract's example error, so read the header, not the body. - No
authorizationheader gets401. Prism checks that a bearer token is present, not what it is. - Example headers. Prism also returns example headers such as
idempotent-replayed: true, which the real API sends only when a request is replayed.
To run the Python and TypeScript code below against the mock, set DEX_BASE_URL=http://127.0.0.1:4010 and any DEX_API_KEY, such as dex_test_mock. The code then prints the answers above.
Make your first call
Send the same request to https://api.thinqit.ai with your key. The curl tab sends request.json from above. The Python and TypeScript tabs build the same request with the SDK helpers. Until the SDKs are on PyPI and npm, install them from Downloads.
curl https://api.thinqit.ai/v1/decide \
-H "authorization: Bearer $DEX_API_KEY" \
-H "content-type: application/json" \
-d @request.json# pip install thinqit-dex (coming soon; until then see Downloads on the SDKs page)
from thinqit_dex import Client, pick, rate, check
client = Client() # reads DEX_API_KEY
decision = client.decide(
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": pick(
"Which team should handle {{ticket.body}}?",
{
"billing": "Payments, refunds and invoices",
"technical": "Bugs, outages and sign-in problems",
"shipping": "Delivery and returns",
"other": None,
},
min_confidence=0.5,
),
"urgency": rate(
"How fast must we reply to {{ticket.body}}?",
["Can wait", "This week", "Within two working days", "Today"],
),
"angry": check("Is the writer of {{ticket.body}} angry?", min_confidence=0.6),
},
)
team = decision.pick("team")
print(team.abstained, team.choice, team.probabilities)
print(decision.rate("urgency").rating)
print(decision.check("angry").probability)// npm i @thinqit/dex (coming soon; until then see Downloads on the SDKs page)
import { Client, pick, rate, check } from "@thinqit/dex";
const client = new Client(); // reads DEX_API_KEY
const decision = await client.decide({
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: pick(
"Which team should handle {{ticket.body}}?",
{
billing: "Payments, refunds and invoices",
technical: "Bugs, outages and sign-in problems",
shipping: "Delivery and returns",
other: null,
},
{ min_confidence: 0.5 },
),
urgency: rate("How fast must we reply to {{ticket.body}}?", [
"Can wait",
"This week",
"Within two working days",
"Today",
]),
angry: check("Is the writer of {{ticket.body}} angry?", { min_confidence: 0.6 }),
},
});
const { team, urgency, angry } = decision.answers;
console.log(team.abstained, team.choice, team.probabilities);
console.log(urgency.rating);
console.log(angry.probability);Read the answer
Illustrative values:
{
"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"
}
}
The answers
| Field | What it tells you |
|---|---|
team.choice |
The most probable option: billing. |
team.probabilities |
One probability per option, in the order you sent them. They sum to exactly 1. |
team.confidence |
For a pick, the gap between the two largest probabilities: 0.9112 minus 0.0383 is 0.8729. |
team.abstained |
false, because 0.8729 is not below your min_confidence of 0.5. |
urgency.rating |
The probability-weighted level index, from 0 (Can wait) to 3 (Today). 2.699 sits between Within two working days and Today, close to Today. |
urgency.probabilities |
One probability per level, aligned with levels. |
urgency.confidence |
How concentrated the probabilities are around the rating. 1 means all on one level. |
urgency.abstained |
Always false here, because this question has no min_confidence. |
angry.probability |
The probability that the answer is yes: 0.9421. |
angry.confidence |
For a check, the distance from 50/50: two times 0.9421 minus 1 is 0.8842. |
Confidence says how concentrated an answer is. It is not a promise that the answer is right. See Confidence.
The envelope
| Field | What it tells you |
|---|---|
id |
The request id. The same value is in the x-request-id header. Quote it to support. |
model |
The exact version that answered. You asked for the alias dex-1, and the response names the version it resolved to. See Models and versions. |
served_by |
gpu for our own engine, fallback for the hosted fallback. See Fallback and served_by. |
calibration |
The calibration version applied to the probabilities. Each one has a public report on the calibration page. |
usage.input_tokens |
The billed tokens: state_tokens (52) plus question_tokens (66). Output is free. |
usage.charge_micro_cents |
What was debited: input_tokens times unit_price_micro_cents, here 118 times 5, is 590 micro-cents, which is EUR 0.0000059. One euro is 100,000,000 micro-cents. Test keys are charged 0. See Tokens and billing. |
Act on it
Use an answer only when it did not abstain. When team abstains, Dex is telling you that two teams are close, so send the ticket to a person instead of guessing. route_to_person and the other functions below stand for your own code.
curl -s https://api.thinqit.ai/v1/decide \
-H "authorization: Bearer $DEX_API_KEY" \
-H "content-type: application/json" \
-d @request.json > response.json
# Prints "person" when the team answer abstained, else the team
jq -r 'if .answers.team.abstained then "person" else .answers.team.choice end' response.jsonteam = decision.pick("team")
if team.abstained:
route_to_person(ticket) # not sure enough: a person decides
else:
route_to_team(ticket, team.choice)
if decision.rate("urgency").rating >= 2.5:
set_priority(ticket, "high")
angry = decision.check("angry")
if not angry.abstained and angry.probability >= 0.5:
add_tag(ticket, "angry")const { team, urgency, angry } = decision.answers;
if (team.abstained) {
routeToPerson(ticket); // not sure enough: a person decides
} else {
routeToTeam(ticket, team.choice);
}
if (urgency.rating >= 2.5) setPriority(ticket, "high");
if (!angry.abstained && angry.probability >= 0.5) addTag(ticket, "angry");If a call fails, the body has an error type and code. See Errors for which ones to retry.
Next steps
- Learn the three question types and how to write good options and levels.
- Choose a threshold with Abstention.
- Read the Known limits before you automate: instructions hidden in the state, sarcasm, date arithmetic and Dutch criteria.
- Pin a version for tests with Determinism.
- Retry safely with Idempotency.
- Check the limits and rate limits.
- Read the full schema in the API reference for decide.