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

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.

View as Markdown

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 whoami

You 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:

  • state is JSON, so each question can point at one field with {{ticket.body}}. See Field references.
  • team is a pick: choose one option. other has no description because its label says enough.
  • urgency is a rate: place the ticket on four levels, listed from lowest to highest. rate is in beta; see Questions.
  • angry is a check: the probability that the statement is true.
  • min_confidence tells an unsure answer to abstain instead of guessing. See Abstention.
  • The ids team, urgency and angry are 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 200 and the contract's first example: the answer to this page's ticket (team is billing), 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. The sl-violations response header lists what is wrong (add -i to curl to see headers). The body is the contract's example error, so read the header, not the body.
  • No authorization header gets 401. 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.json
team = 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