Cookbook
Recipe: moderate marketplace posts
Name the rule a listing breaks, rate how severe it is and check for someone else's personal details, then remove clear scams and queue the rest for reviewers.
Every new listing gets checked before buyers see it, not a sample. One request names the rule the post breaks, rates how bad it is and checks whether it exposes someone else's details. Your code removes a post by itself only when the answer is clear, and reviewers see the rest.
The request
fallback: "never". The hosted fallback runs behind content filters that block exactly this kind of text, so a moderation request that reached it would fail anyway. Withneverthe request stays on the GPU path, where no content filter applies, and gets a quick503withretry-afterwhen no GPU is free. See Fallback and served_by.- State. The post, plus two facts about the seller that the model reads next to it: the account is 2 days old and has no earlier listings. Compute facts like these in your own code and pass them as fields.
ruleis apickwithnoneas the way out. The twocriteriasettle the edge cases for your marketplace: payment by gift card or outside the checkout is a scam, and sellers may share their own contact details.severityis aratein beta, with nomin_confidence, so it never abstains. The code below only uses it to order the review queue, never to remove a post.personal_detailsis acheckabout someone other than the seller, with a floor of 0.6.
{
"fallback": "never",
"state": {
"post": {
"board": "Phones for sale",
"title": "Brand new phone, sealed, half price",
"text": "Sealed flagship phone, still in its box, EUR 250 today only. Pay first with gift cards and I ship the same day. Do not bother with the platform checkout, message me on WhatsApp instead."
},
"seller": { "account_age_days": 2, "earlier_listings": 0 }
},
"questions": {
"rule": {
"type": "pick",
"instructions": "Which marketplace rule does {{post.text}} break, if any?",
"criteria": [
"Asking for payment by gift card or outside the platform checkout counts as a scam.",
"Sharing your own contact details in your own listing is allowed."
],
"options": {
"none": "Breaks no rule",
"scam": "Fraud, fake offers or payment outside the platform",
"harassment": "Threats, intimidation or exposing someone's personal details",
"spam": "Repeated or unsolicited promotion",
"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"]
},
"personal_details": {
"type": "check",
"instructions": "Does {{post.text}} reveal personal details of someone other than the seller, such as a home address or phone number?",
"min_confidence": 0.6
}
}
}
Run it
Put a test key in DEX_API_KEY (see the Quickstart). Save the Python code as moderation.py and run python moderation.py. Save the TypeScript code as moderation.mts and run npx tsx moderation.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'
{
"fallback": "never",
"state": {
"post": {
"board": "Phones for sale",
"title": "Brand new phone, sealed, half price",
"text": "Sealed flagship phone, still in its box, EUR 250 today only. Pay first with gift cards and I ship the same day. Do not bother with the platform checkout, message me on WhatsApp instead."
},
"seller": { "account_age_days": 2, "earlier_listings": 0 }
},
"questions": {
"rule": {
"type": "pick",
"instructions": "Which marketplace rule does {{post.text}} break, if any?",
"criteria": [
"Asking for payment by gift card or outside the platform checkout counts as a scam.",
"Sharing your own contact details in your own listing is allowed."
],
"options": {
"none": "Breaks no rule",
"scam": "Fraud, fake offers or payment outside the platform",
"harassment": "Threats, intimidation or exposing someone's personal details",
"spam": "Repeated or unsolicited promotion",
"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"]
},
"personal_details": {
"type": "check",
"instructions": "Does {{post.text}} reveal personal details of someone other than the seller, such as a home address or phone number?",
"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'''
{
"fallback": "never",
"state": {
"post": {
"board": "Phones for sale",
"title": "Brand new phone, sealed, half price",
"text": "Sealed flagship phone, still in its box, EUR 250 today only. Pay first with gift cards and I ship the same day. Do not bother with the platform checkout, message me on WhatsApp instead."
},
"seller": { "account_age_days": 2, "earlier_listings": 0 }
},
"questions": {
"rule": {
"type": "pick",
"instructions": "Which marketplace rule does {{post.text}} break, if any?",
"criteria": [
"Asking for payment by gift card or outside the platform checkout counts as a scam.",
"Sharing your own contact details in your own listing is allowed."
],
"options": {
"none": "Breaks no rule",
"scam": "Fraud, fake offers or payment outside the platform",
"harassment": "Threats, intimidation or exposing someone's personal details",
"spam": "Repeated or unsolicited promotion",
"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"]
},
"personal_details": {
"type": "check",
"instructions": "Does {{post.text}} reveal personal details of someone other than the seller, such as a home address or phone number?",
"min_confidence": 0.6
}
}
}
''')
decision = client.decide(
request["state"],
request["questions"],
fallback=request["fallback"],
)
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(`{
"fallback": "never",
"state": {
"post": {
"board": "Phones for sale",
"title": "Brand new phone, sealed, half price",
"text": "Sealed flagship phone, still in its box, EUR 250 today only. Pay first with gift cards and I ship the same day. Do not bother with the platform checkout, message me on WhatsApp instead."
},
"seller": { "account_age_days": 2, "earlier_listings": 0 }
},
"questions": {
"rule": {
"type": "pick",
"instructions": "Which marketplace rule does {{post.text}} break, if any?",
"criteria": [
"Asking for payment by gift card or outside the platform checkout counts as a scam.",
"Sharing your own contact details in your own listing is allowed."
],
"options": {
"none": "Breaks no rule",
"scam": "Fraud, fake offers or payment outside the platform",
"harassment": "Threats, intimidation or exposing someone's personal details",
"spam": "Repeated or unsolicited promotion",
"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"]
},
"personal_details": {
"type": "check",
"instructions": "Does {{post.text}} reveal personal details of someone other than the seller, such as a home address or phone number?",
"min_confidence": 0.6
}
}
}`);
const decision = await client.decide(request);
console.log(decision.answers);Expected output
{
"id": "req_01M3JE24DES9WY9MBW6EXX490X",
"object": "decision",
"created": 1790546350,
"model": "dex-1.0.1",
"served_by": "gpu",
"calibration": "cal-20260926-1",
"answers": {
"rule": {
"type": "pick",
"choice": "scam",
"probabilities": {
"none": 0.0296,
"scam": 0.913,
"harassment": 0.0274,
"spam": 0.0162,
"hate": 0.0138
},
"confidence": 0.8833,
"abstained": false
},
"severity": {
"type": "rate",
"rating": 2.1249,
"levels": ["None", "Mild", "Serious", "Severe"],
"probabilities": [0.034, 0.1727, 0.4276, 0.3657],
"confidence": 0.4589,
"abstained": false
},
"personal_details": {
"type": "check",
"probability": 0.0752,
"confidence": 0.8497,
"abstained": false
}
},
"usage": {
"input_tokens": 232,
"state_tokens": 86,
"question_tokens": 146,
"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, 232 input tokens (86 for the state, 146 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 |
|---|---|---|---|---|---|
rule |
pick | scam (0.913) |
0.8833 | 0.6 | no |
severity |
rate | rating 2.1249, most likely Serious (0.4276) |
0.4589 | none | no |
personal_details |
check | yes with probability 0.0752 | 0.8497 | 0.6 | no |
The post breaks the scam rule with probability 0.913. The severity rating of 2.1249 sits between Serious and Severe, and its confidence of 0.4589 shows how spread out it is. The post exposes no one else's details (0.0752), so the scam is the only problem.
Act on it
post_id and the functions queue_for_review, remove_post and set_review_priority stand for your own code. A post is removed without a reviewer only when one rule has a probability of 0.9 or more. Here scam has 0.913, so the post comes down at once, and its review gets a high priority because the severity rating is above 2.
rule = decision.pick("rule")
if rule.abstained:
queue_for_review(post_id, "unsure") # two rules are close: a reviewer decides
elif rule.choice != "none" and rule.probabilities[rule.choice] >= 0.9:
remove_post(post_id, rule.choice) # a clear case: remove now, review later
elif rule.choice != "none":
queue_for_review(post_id, rule.choice)
severity = decision.rate("severity")
if not severity.abstained and severity.rating >= 2:
set_review_priority(post_id, "high")
details = decision.check("personal_details")
if details.abstained or details.probability >= 0.5:
queue_for_review(post_id, "personal details")const { rule, severity, personal_details: details } = decision.answers;
if (rule?.type === "pick") {
if (rule.abstained) queueForReview(postId, "unsure"); // two rules are close: a reviewer decides
else if (rule.choice !== "none" && rule.probabilities[rule.choice] >= 0.9) removePost(postId, rule.choice); // a clear case
else if (rule.choice !== "none") queueForReview(postId, rule.choice);
}
if (severity?.type === "rate" && !severity.abstained && severity.rating >= 2) setReviewPriority(postId, "high");
if (details?.type === "check" && (details.abstained || details.probability >= 0.5)) queueForReview(postId, "personal details");An abstained personal_details answer sends the post to a reviewer: that is the safe route, not an action on the answer. As in the support recipe, the TypeScript answers have the general Answer type, so the code narrows each one on type.
Adapt it
- Set the removal bar on your own posts. Run a few hundred posts your reviewers already judged with a test key, and raise the bar until automatic removals are right often enough. Content moderation is the weakest domain in our behaviour suite, so review removals too. See Known limits.
- Your own rules. Add an option per rule (up to 255) and keep
none. Write each edge case as a criterion in English. - Sellers write the text you judge. Text in the state can try to steer the answer. Add a guardrail check as shown in Known limits and send those posts to a reviewer.
- Pin a version when removals must not shift between runs; an exact version also never uses the fallback. See Determinism.