Cookbook
Recipe: score inbound leads
Pick what the sender wants, rate how well the company fits your ideal customer and check whether they ask for a call, then fill the CRM fields and book the call.
Every inbound form gets an intent, a fit score and a meeting flag as it arrives, and sales starts from filled CRM fields. On the same model version the same lead always gets the same score, so the ranking holds from one day to the next.
The request
- State. The form as JSON and the page it came from.
intentandwants_meetingpoint at{{form.message}}, whilefitpoints at the whole{{form}}object so it reads the company size and country too. Leave out what the decision does not need, such as the email address. intentis apickof five reasons to write. They are mutually exclusive, so one pick is better than five checks, andspamis the way out.fitis aratein beta on four levels. Thecriteriadefine your ideal customer profile in one sentence: an EU company with more than 50 employees that handles thousands of messages, tickets or documents a month. Change the sentence and the scale follows.wants_meetingis acheck: one yes or no fact about the message.min_confidenceon every question, so an unsure answer leaves its CRM field empty instead of guessing.
{
"state": {
"form": {
"name": "Sanne de Vries",
"company": "Noordhaven Logistics",
"company_size": "250 employees",
"country": "NL",
"message": "We route about 40,000 customer emails a month by hand and want to automate the first triage. Can we get pricing for that volume and set up a call next week?"
},
"source": "pricing page contact form"
},
"questions": {
"intent": {
"type": "pick",
"instructions": "What does the sender of {{form.message}} want?",
"options": {
"buy": "Wants to buy or asks about pricing",
"partner": "Proposes a partnership or reselling",
"support": "Needs help with an existing account",
"job": "Applies for a job",
"spam": "Unsolicited sales pitch or nonsense"
},
"min_confidence": 0.5
},
"fit": {
"type": "rate",
"instructions": "How well does the company in {{form}} fit our ideal customer?",
"criteria": "Our ideal customer is an EU company with more than 50 employees that handles thousands of messages, tickets or documents a month.",
"levels": ["Poor", "Fair", "Good", "Excellent"],
"min_confidence": 0.3
},
"wants_meeting": {
"type": "check",
"instructions": "Does the sender of {{form.message}} ask for a call or a meeting?",
"min_confidence": 0.6
}
}
}
Run it
Put a test key in DEX_API_KEY (see the Quickstart). Save the Python code as lead_scoring.py and run python lead_scoring.py. Save the TypeScript code as lead-scoring.mts and run npx tsx lead-scoring.mts: the code uses await at the top level, and the .mts ending makes the file an ES module. Install the SDKs from Downloads.
curl https://api.thinqit.ai/v1/decide \
-H "authorization: Bearer $DEX_API_KEY" \
-H "content-type: application/json" \
--data-binary @- <<'DEX_REQUEST'
{
"state": {
"form": {
"name": "Sanne de Vries",
"company": "Noordhaven Logistics",
"company_size": "250 employees",
"country": "NL",
"message": "We route about 40,000 customer emails a month by hand and want to automate the first triage. Can we get pricing for that volume and set up a call next week?"
},
"source": "pricing page contact form"
},
"questions": {
"intent": {
"type": "pick",
"instructions": "What does the sender of {{form.message}} want?",
"options": {
"buy": "Wants to buy or asks about pricing",
"partner": "Proposes a partnership or reselling",
"support": "Needs help with an existing account",
"job": "Applies for a job",
"spam": "Unsolicited sales pitch or nonsense"
},
"min_confidence": 0.5
},
"fit": {
"type": "rate",
"instructions": "How well does the company in {{form}} fit our ideal customer?",
"criteria": "Our ideal customer is an EU company with more than 50 employees that handles thousands of messages, tickets or documents a month.",
"levels": ["Poor", "Fair", "Good", "Excellent"],
"min_confidence": 0.3
},
"wants_meeting": {
"type": "check",
"instructions": "Does the sender of {{form.message}} ask for a call or a meeting?",
"min_confidence": 0.6
}
}
}
DEX_REQUEST# Save as dex_request.py, then run: python dex_request.py
import json
from thinqit_dex import Client
client = Client() # reads DEX_API_KEY from the environment
request = json.loads(r'''
{
"state": {
"form": {
"name": "Sanne de Vries",
"company": "Noordhaven Logistics",
"company_size": "250 employees",
"country": "NL",
"message": "We route about 40,000 customer emails a month by hand and want to automate the first triage. Can we get pricing for that volume and set up a call next week?"
},
"source": "pricing page contact form"
},
"questions": {
"intent": {
"type": "pick",
"instructions": "What does the sender of {{form.message}} want?",
"options": {
"buy": "Wants to buy or asks about pricing",
"partner": "Proposes a partnership or reselling",
"support": "Needs help with an existing account",
"job": "Applies for a job",
"spam": "Unsolicited sales pitch or nonsense"
},
"min_confidence": 0.5
},
"fit": {
"type": "rate",
"instructions": "How well does the company in {{form}} fit our ideal customer?",
"criteria": "Our ideal customer is an EU company with more than 50 employees that handles thousands of messages, tickets or documents a month.",
"levels": ["Poor", "Fair", "Good", "Excellent"],
"min_confidence": 0.3
},
"wants_meeting": {
"type": "check",
"instructions": "Does the sender of {{form.message}} ask for a call or a meeting?",
"min_confidence": 0.6
}
}
}
''')
decision = client.decide(
request["state"],
request["questions"],
)
for question_id, answer in decision.answers.items():
print(question_id, answer)// Save as dex-request.mts, then run: npx tsx dex-request.mts (Node.js 18 or newer)
import { Client, parseRequest } from "@thinqit/dex";
const client = new Client(); // reads DEX_API_KEY from the environment
// parseRequest keeps the key order of the text (JSON.parse would move labels such as "1" to the front).
const request = parseRequest(`{
"state": {
"form": {
"name": "Sanne de Vries",
"company": "Noordhaven Logistics",
"company_size": "250 employees",
"country": "NL",
"message": "We route about 40,000 customer emails a month by hand and want to automate the first triage. Can we get pricing for that volume and set up a call next week?"
},
"source": "pricing page contact form"
},
"questions": {
"intent": {
"type": "pick",
"instructions": "What does the sender of {{form.message}} want?",
"options": {
"buy": "Wants to buy or asks about pricing",
"partner": "Proposes a partnership or reselling",
"support": "Needs help with an existing account",
"job": "Applies for a job",
"spam": "Unsolicited sales pitch or nonsense"
},
"min_confidence": 0.5
},
"fit": {
"type": "rate",
"instructions": "How well does the company in {{form}} fit our ideal customer?",
"criteria": "Our ideal customer is an EU company with more than 50 employees that handles thousands of messages, tickets or documents a month.",
"levels": ["Poor", "Fair", "Good", "Excellent"],
"min_confidence": 0.3
},
"wants_meeting": {
"type": "check",
"instructions": "Does the sender of {{form.message}} ask for a call or a meeting?",
"min_confidence": 0.6
}
}
}`);
const decision = await client.decide(request);
console.log(decision.answers);Expected output
{
"id": "req_01M3JE1VPZ9WWB18Z4F27NS6C9",
"object": "decision",
"created": 1790546341,
"model": "dex-1.0.1",
"served_by": "gpu",
"calibration": "cal-20260926-1",
"answers": {
"intent": {
"type": "pick",
"choice": "buy",
"probabilities": {
"buy": 0.9433,
"partner": 0.0159,
"support": 0.0165,
"job": 0.0116,
"spam": 0.0127
},
"confidence": 0.9268,
"abstained": false
},
"fit": {
"type": "rate",
"rating": 2.3044,
"levels": ["Poor", "Fair", "Good", "Excellent"],
"probabilities": [0.0127, 0.0362, 0.585, 0.3661],
"confidence": 0.5997,
"abstained": false
},
"wants_meeting": {
"type": "check",
"probability": 0.9458,
"confidence": 0.8915,
"abstained": false
}
},
"usage": {
"input_tokens": 216,
"state_tokens": 87,
"question_tokens": 129,
"allowance_tokens": 0,
"paid_tokens": 0,
"charge_micro_cents": 0,
"unit_price_micro_cents": 0,
"tier": "test"
}
}
Captured from the live API on 2026-09-27 with a test key: model dex-1.0.1, calibration cal-20260926-1, served_by: gpu, 216 input tokens (87 for the state, 129 for the questions). A test key is charged nothing, so tier is test and the charge is 0. On this exact version the same request always returns these answers.
| Question | Type | Answer | Confidence | min_confidence |
Abstained |
|---|---|---|---|---|---|
intent |
pick | buy (0.9433) |
0.9268 | 0.5 | no |
fit |
rate | rating 2.3044, most likely Good (0.585) |
0.5997 | 0.3 | no |
wants_meeting |
check | yes with probability 0.9458 | 0.8915 | 0.6 | no |
The sender wants to buy (0.9433) and asks for a call (0.9458). The fit rating of 2.3044 is closest to Good (0.585), with Excellent second at 0.3661. No answer abstained, so every CRM field gets a value.
Act on it
lead_id and the functions update_crm and book_call stand for your own code. Each typed answer fills one CRM field: the pick's label, the nearest level of the rating (2.3044 rounds to 2, which is Good) and a yes or no for the meeting. Here all three are filled, and the lead wants to buy, fits well and asks for a call, so the code books it.
crm = {}
intent = decision.pick("intent")
if not intent.abstained:
crm["intent"] = intent.choice
fit = decision.rate("fit")
if not fit.abstained:
crm["fit"] = fit.levels[round(fit.rating)] # the nearest level
meeting = decision.check("wants_meeting")
if not meeting.abstained:
crm["wants_meeting"] = meeting.probability >= 0.5
update_crm(lead_id, crm) # an abstained answer leaves its field empty for sales
if crm.get("intent") == "buy" and crm.get("fit") in ("Good", "Excellent") and crm.get("wants_meeting"):
book_call(lead_id)const { intent, fit, wants_meeting: meeting } = decision.answers;
const crm: Record<string, string | boolean> = {};
if (intent?.type === "pick" && !intent.abstained) crm.intent = intent.choice;
if (fit?.type === "rate" && !fit.abstained) crm.fit = fit.levels[Math.round(fit.rating)]; // the nearest level
if (meeting?.type === "check" && !meeting.abstained) crm.wants_meeting = meeting.probability >= 0.5;
updateCrm(leadId, crm); // an abstained answer leaves its field empty for sales
if (crm.intent === "buy" && (crm.fit === "Good" || crm.fit === "Excellent") && crm.wants_meeting === true) bookCall(leadId);As in the support recipe, the TypeScript answers have the general Answer type, so the code narrows each one on type.
Adapt it
- Your profile, in one sentence. Put what makes a good customer in
criteria, in English, as facts a form can show: country, size, volume. Dex reads the form; it does not look the company up. When you have data from your own sources, pass it as state fields. - Rank, not only label. Store
fit.ratingas a number from 0 to 3 next to the level name, and sort the sales queue on it. - Forms are written by strangers. Text in the state can try to steer the answer. Add a guardrail check as in the agent tool gate recipe before a score triggers anything you cannot undo.
- Tune on leads you already know. Run past forms whose outcome you know with a test key, and move the floors and the booking rule until the booked calls are the ones sales wants. See Abstention.