SCREENING API

Stop fake signups, spam and prompt injections. One API call.

Send an email, a link or a block of text. Get back clean, suspect or block, with the reason.

INPUTPOST /v1/emails
GATE
Runs live against the real API · no account needed
try
200 OK48 ms (api)
BLOCK
Domain "mailinator.com" is a known disposable/throwaway email provider
6 of 9 checks
CHECKRESULTDATA
code
email.disposable_domain
message
Domain "mailinator.com" is a known disposable/throwaway email provider
severity
high

Live call to the same /v1 endpoint customers use. Rules only on this page; nothing you type is stored.

PUT IT IN FRONT OF
Signup formsContact formsComment boxesSubmitted linksSupport tickets and pastesLLM inputs
HOW A GATE WORKS

One input in.
Every check at once.
One verdict out.

POST /v1/emails
sam@mailinator.com
syntaxclean
mxclean
disposablefinding
role_accountclean
BLOCK1 finding: email.disposable_domain (high)
1
Send one request
JSON in, JSON out, over POST. A body is at most 4 KiB for the identity gates and up to 64 KiB for text gates; nothing goes in a URL or query string.
2
Every check runs at once
There’s no depth parameter and no check to pick. The gate runs everything you’re entitled to, concurrently, and a slow check is cut off so the response still ships.
3
Branch on one verdict
clean means no findings. suspect means the worst finding was low or medium. block means high or critical. No score, no threshold to tune.
GATES

A gate for each thing
strangers hand you.

Each gate is its own endpoint with its own checks. Eleven are live. Every plan runs every rule check; three gates add a model check on top, labelled separately in the response so you can ignore it.

Every finding code has a docs page: mechanism, risk, fix. /docs

THE RESPONSE

One envelope.
Every gate.

Same shape on every path, so one integration covers all eleven. Findings carry a stable code, a fixed severity and a source, and the verdict is the highest severity present.

verdict
clean, suspect or block. Branch on this.
findings[]
One per problem. source is rules or model, so you can act on rules alone.
checks_run
Exactly what ran, at check granularity.
checks_skipped
What didn’t run and why — input (send the IP and you get five more checks), plan, error, or unavailable. Never forward this to your end user.
data
Derived values you may want, like the normalised address. Absent when the producing check didn’t run, never null.
REQUEST
curl -X POST https://api.stormdoor.dev/v1/emails \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{"email":"sam@mailinator.com"}'
RESPONSE200 OK
{
  "ok": true,
  "verdict": "block",
  "findings": [
    {
      "code": "email.disposable_domain",
      "severity": "high",
      "source": "rules",
      "message": "Domain \"mailinator.com\" is a known disposable/throwaway email provider"
    }
  ],
  "checks_run": ["email.alias_normalisation", "email.disposable", "email.mx",
                 "email.role_account", "email.syntax", "email.typo_domain"],
  "checks_skipped": [
    { "code": "ip.hosting_provider", "reason": "input", "requires": ["ip"] },
    { "code": "ip.not_public", "reason": "input", "requires": ["ip"] },
    { "code": "ip.tor_exit", "reason": "input", "requires": ["ip"] }
  ],
  "meta": { "gate": "emails", "ms": 48, "ruleset": "reference-9f0e0204" }
}
THE HONEST PARTS

A screening API that overstates itself is the thing you are screening against.

It fails open, and says so.
Out of credits, an internal error or an unavailable gate returns 402, 500 or 503 with fail_open: true and no verdict at all. Your form keeps working; you decide what to do with "not screened". It’s in the docs, not discovered during an outage.
It tells you what it skipped.
checks_skipped names every check that didn’t run and the reason. A shallower answer is always labelled as one.
Rules first, models second.
Every gate returns its rule-based findings first. The two model-backed checks are additive, carry source: "model", and are capped at medium, so a model alone can never produce block.
No score.
Severities are fixed per finding and documented. A verdict is the highest severity present.

/v1/emails does not verify that a mailbox exists. When it can, it will say so here.

YOUR DATA

We look.
We do not keep.

Screening what strangers send you should not create a new pile of personal data.

Nothing written to disk.
Request bodies are processed in memory and dropped. Not a database, not object storage, not a log line.
Errors never echo your input.
A rejected request names the field that failed, never the value you sent, so a validation error cannot leak what you screened.
Secrets never come back.
/v1/secrets returns the offset and the pattern name. A password we echoed would be a password we handled twice.
The API runs in Frankfurt (EU).
The screening API itself runs in Frankfurt. The docs and this site are served from Cloudflare’s edge worldwide, and the two model checks send the text to a third-party model provider.

POST only, JSON only. No file uploads. No query strings. Calls from a browser are rejected, so your key stays on your server.

QUESTIONS

The rest,
answered.

You get 503 with fail_open: true and no verdict. Nothing was screened, and the response says so rather than claiming clean. The documented default is to let the form proceed.

Put a door in front
of what strangers send.

Signups open with paid plans. Join the waitlist and try every gate on this page meanwhile.

WHAT WOULD YOU USE IT FOR?