docs
stormdoor.dev
GATE

/v1/ip

Screens an IP address for reserved/private ranges, cloud-hosting origin, VPN ranges, and Tor exit nodes.

METHOD
POST
BODY CAP
4 KiB
CHECKS
5
FINDING CODES
4

POST /v1/ip screens one IP address, IPv4 or IPv6. Use it on the signup or contact-form IP a caller already has — most integrations send it alongside an email address via /v1/emails’s optional ip field or /v1/signups, rather than calling /v1/ip on its own, but it’s a real gate in its own right.

Request#

{ "ip": "8.8.8.8" }
Field Type Required Notes
ip string yes IPv4 or IPv6. An unparseable value is a 400 invalid_request, not a finding — this is the gate’s one required input.

There are no optional fields yet.

Request body cap: 4 KB — far more than a single address needs, with headroom for future optional fields.

Checks this gate runs#

Every check here is pure rule-based computation — no network calls, no model inference — so all of them are free on every plan (CLAUDE.md design rule 4: “Never withhold a zero-marginal-cost check”).

Check What it does
ip.not_public Private, loopback, link-local, carrier-grade NAT, documentation/benchmarking and reserved ranges — see ip.not_public.
ip.hosting_provider Match against published AWS/GCP/Oracle/DigitalOcean/Azure IP ranges — see ip.hosting_provider. Also contributes data.hosting_provider.
ip.tor_exit Match against the Tor Project’s bulk exit-node list — see ip.tor_exit.
ip.vpn_provider Match against a list of known commercial VPN ranges — see ip.vpn_provider.
ip.asn Contributes data.asn (the autonomous system number) and data.asn_org (its registered name) for routed addresses. Emits no findings.

The VPN list is open-source and incomplete by nature; it catches the common providers, not all of them. Paid residential-proxy and VPN reputation is a separate, later check.

Example#

curl -s -X POST https://api.stormdoor.dev/v1/ip \
  -H "Authorization: Bearer sk_live_..." \
  -H "content-type: application/json" \
  -d '{"ip":"3.5.140.1"}'
{
  "ok": true,
  "verdict": "suspect",
  "findings": [
    {
      "code": "ip.hosting_provider",
      "severity": "medium",
      "message": "\"3.5.140.1\" is in a published aws IP range"
    }
  ],
  "data": { "hosting_provider": "aws", "asn": 16509, "asn_org": "AMAZON-02" },
  "checks_run": ["ip.asn", "ip.hosting_provider", "ip.not_public", "ip.tor_exit", "ip.vpn_provider"],
  "checks_skipped": [],
  "meta": { "gate": "ip", "ruleset": "reference-..." }
}

See response format for how checks_skipped reasons work, and don’t proxy that array 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.

ip.hosting_providermediumip.hosting_providersuspect
ip.not_publiclowip.not_publicsuspect
ip.tor_exitmediumip.tor_exitsuspect
ip.vpn_providerlowip.vpn_providersuspect