docs
stormdoor.dev
REFERENCE

Response format

One envelope, identical across every gate.

{
  "ok": true,
  "verdict": "clean" | "suspect" | "block",
  "findings": [
    {
      "code": "email.typo_domain",
      "severity": "low" | "medium" | "high" | "critical",
      "message": "Domain \"gmial.com\" looks like a typo of \"gmail.com\"",
      "location": "gmial.com"
    }
  ],
  "data": { "normalised": "bob@gmail.com" },
  "checks_run": ["email.syntax", "email.role_account", "..."],
  "checks_skipped": [
    { "code": "email.mx", "reason": "error" }
  ],
  "meta": { "gate": "emails", "ms": 41, "ruleset": "2026.08.19" }
}

Verdict is derived, not scored#

There's no numeric score. Every finding carries a fixed severity, and verdict is the highest severity present: no findings is clean, low/medium is suspect, high/critical is block.

A finding always moves the verdict — there's no informational tier. Emitting a finding at all is already saying the input isn't clean. Neutral observations that aren't a risk signal go in data instead — for example, a normalised email address is a fact about the input, not a finding.

Findings#

Each finding has a stable, namespaced code (documented on its own page — see each gate's page for the full list), a severity, a human-readable message, and an optional location pointing at the specific part of the input that triggered it.

data is gate-specific output, not findings#

data holds values a gate resolved while checking your input — a normalised address, a resolved redirect chain, a domain's registration age — typed per gate and documented on that gate's page. A field is absent, not null, whenever the check that would have produced it didn't run. Don't treat a missing field as a negative result; check checks_skipped to see whether it ran at all.

checks_run and checks_skipped#

checks_run lists every check that actually executed. checks_skipped lists every check that didn't, with one of four reasons:

  • plan — the check exists but isn't available on your current plan.
  • input — the check needs an optional field you didn't send.
  • error — the check ran, failed or timed out, and the gate returned its partial result rather than failing the whole call.
  • unavailable — a check this one depends on was itself skipped or failed, so this one never ran.

Don't proxy a raw checks_skipped array through to your own end users. It's a map of exactly what wasn't checked — useful for you to see what you'd gain from a different plan or a richer request, but a reconnaissance map if shown to whoever you're screening.