Spam detection
Classify free text as allow, review or block and estimate its spam probability with the Spam Detection template — a decision with a text state.
Goal: screen user-generated text — comments, messages, sign-up bios — before it's published: allow the legitimate, block clear spam and send the unclear to a moderator.
Create the decision
Templates → Spam Detection → Create decision:
| Part | Content |
|---|---|
| State | text — the request's state is the message itself |
spam_probability | probability — is this message spam, phishing or an unsolicited promotion? |
action | choice — allow (legitimate message), review (unclear, a moderator should check), block (clear spam, scam or abuse), other |
| Policies | 1. action = block → block |
Call it
With a text state, state is a string:
curl -X POST https://api.dcision.io/v1/decisions/spam-detection \
-H "Authorization: Bearer $DCISION_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: comment-77310" \
-d '{ "state": "Congratulations! You won a $1000 gift card, click here to claim now." }'{
"decision_id": "dec_Vx7pL2qR9mTz4Kc8Wn1J",
"execution_id": "exec_Hd3sQ8wF1yLm6Pz2Rk9T",
"schema": "spam-detection",
"version": 1,
"result": { "spam_probability": 0.9731, "action": "block" },
"confidence": { "spam_probability": 0.9462, "action": 0.9067 },
"action": "block",
"action_reason": { "type": "policy", "rule": 0 },
"metrics": { "latency_ms": 287, "engine": "jev", "model": "jev-1.13.0", "estimated_cost_usd": 0.0000063, "input_tokens": 150, "output_tokens": 19 }
}Two different actions
result.action is the answer to the template's question named action (allow, review or block). The top-level action is what the policies decided. Here both say block because rule 0 maps one to the other.
An empty string or a JSON object is rejected before the engine runs: 422 INVALID_STATE — "Field state must be a non-empty string."
Make review actionable
Out of the box, a review answer matches no policy and returns continue. Add a rule so moderation is driven by the top-level action, and a probability-based safety net:
[
{ "field": "action", "on": "output", "operator": "eq", "value": "block", "action": "block" },
{ "field": "spam_probability", "on": "output", "operator": "gte", "value": 0.9, "action": "block" },
{ "field": "action", "on": "output", "operator": "eq", "value": "review", "action": "escalate" }
]Then your code only needs the top-level action:
const decision = await response.json();
switch (decision.action) {
case "block":
return comments.reject(comment, { execution_id: decision.execution_id });
case "escalate":
return moderation.enqueue(comment, { spamProbability: decision.result.spam_probability });
default:
return comments.publish(comment);
}Tune it
- State your policy in the decision's
context: what is allowed on your platform (self-promotion? links? other languages?). - Split the judgment. "Is this spam?" hides several questions. Ask the signals separately — does the message ask for a password, promise an unexpected reward, pressure the reader to act now, link to a domain that doesn't match the sender? — and combine them in a composite or in rules. See Writing good questions.
- Expect adversarial text. Spam is written to look legitimate: test edge cases in the Playground before you deploy, and be explicit in the option descriptions.
- Describe
other— for example "not spam, but off-topic or in an unsupported language" — so off-topic content reaches the fallback action instead of being forced intoallow. - Keep messages out of logs when they may contain personal data: turn off
storeInput. - Batch carefully: each message is one decision and one request. Stay under your rate limit by queueing large backfills.