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:

PartContent
Statetext — the request's state is the message itself
spam_probabilityprobability — is this message spam, phishing or an unsolicited promotion?
actionchoice — allow (legitimate message), review (unclear, a moderator should check), block (clear spam, scam or abuse), other
Policies1. 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." }'
200 OK
{
  "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 into allow.
  • 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.

On this page