/v1/spam
A rule-based spam signal layer over free text — links, invisible characters, mixed-script evasion, shouting and known spam phrasing.
- METHOD
- POST
- BODY CAP
- 16 KiB
- CHECKS
- 10
- FINDING CODES
- 8
POST /v1/spam runs a free rule-based layer over a block of text — a comment, a form
submission, a chat message — looking for common spam and evasion signals. This is the
free layer only (D-79); a model-backed classifier is a later, paid addition on top of the
same route.
Request#
{ "text": "check this out https://a.example http://b.example http://c.example", "languages": ["en"] }
| Field | Type | Required | Notes |
|---|---|---|---|
text |
string | yes | Bounded only by this gate’s 16 KB request-body cap (D-80) — no separate limit here. That’s roughly 15,000 characters of English text; fewer for text with accents or emoji. |
languages |
array of strings | no | ISO 639-1 codes, up to 10 — same field as /v1/profanity. Decides which language’s spam.known_phrase list applies and unlocks text.unexpected_language. |
No redact field — this gate scores properties of the message as a whole rather than
finding specific values worth masking.
Checks this gate runs#
| Check | What it does |
|---|---|
spam.link_density |
Fires when a message is mostly links — see spam.link_density. |
spam.invisible_characters |
Zero-width and bidi-control characters — see spam.invisible_characters. |
spam.mixed_script |
A single word mixing Latin with look-alike Cyrillic/Greek letters — see spam.mixed_script. |
spam.shouting |
An almost entirely capital-letters message — see spam.shouting. |
spam.known_phrase |
Matches curated spam/scam phrasing — see spam.known_phrase. |
spam.sales_pitch |
Cold sales-pitch templates aimed at site owners, English only — see spam.sales_pitch. |
spam.off_platform_contact |
An invitation to continue on Telegram, WhatsApp, Signal or Skype — see spam.off_platform_contact. |
text.language |
Identifies the dominant language, when confident. Producer only — emits no finding. |
text.unexpected_language |
Fires when the text is confidently written in a language outside your declared languages. Only runs when you send languages. |
Every check here is pure rule-based text scanning and rune classification, plus the same
compiled-in trigram language detector /v1/profanity uses — no model call, no network —
so every rule check here is free on every plan (design rule 4).
Thresholds are deliberately conservative#
Every finding here moves the verdict (design rule 7), so each check is tuned to avoid firing on ordinary text: a single link never counts as link spam, a handful of capitalised words never counts as shouting, and mixed scripts only matter within one word, never across a whole sentence. See each finding’s own docs page for its exact thresholds and the reasoning behind them.
Example#
curl -s -X POST https://api.stormdoor.dev/v1/spam \
-H "Authorization: Bearer sk_live_..." \
-H "content-type: application/json" \
-d '{"text":"CLICK HERE NOW http://a.example http://b.example http://c.example","languages":["en"]}'
{
"ok": true,
"verdict": "suspect",
"findings": [
{ "code": "spam.link_density", "severity": "low", "message": "message is mostly links rather than text" },
{ "code": "spam.shouting", "severity": "low", "message": "message is almost entirely capital letters" }
],
"data": { "matches": [] },
"checks_run": ["spam.invisible_characters", "spam.known_phrase", "spam.link_density", "spam.mixed_script", "spam.shouting", "text.language", "text.unexpected_language"],
"checks_skipped": [],
"meta": { "gate": "spam", "ruleset": "reference-..." }
}
See response format for how checks_skipped reasons work, and don’t
proxy that array — or the raw match offsets — to your own end users.
Finding codes#
Each code links to its own page with what it looks at, why it matters and what to do.