IP Intelligence

IP reputation API: GET one IP and get a 0-100 risk score, class + risk tags (datacenter, VPN, proxy, Tor, scanner), threat-blocklist hits (Spamhaus, botnet C2), ASN, geo, and city.

A single IP reputation API call returns a 0-100 risk score and the address’s class and risk tags — datacenter, VPN, proxy, Tor, residential proxy, scanner — plus threat-blocklist hits (Spamhaus DROP, botnet C2, abuse reporters), ASN, geo, and optional company, reverse-DNS, and city enrichment.

You can see the IP on every request, but a bare address tells you nothing about whether it’s a home connection or a rented VPS running a scanner. Stitching together a geo database, an ASN feed, and a proxy/VPN list yourself means maintaining three data sources and still missing residential proxies and fresh Tor exits. IP Intelligence collapses that into one authenticated GET.

When to use it

Reach for GET /v1/ip/{ip} whenever you have an address and want to know what kind of network it belongs to before you trust it — at signup, on a login, gating a checkout, or enriching a log line. A datacenter- or cloud-classed IP hitting a consumer signup form is rarely a real customer; a Tor or residential-proxy tag on a payment attempt is worth a second look.

It complements the beacon and server reporting, which score full requests. IP Intelligence is the narrow, synchronous lookup you call yourself when all you have — and all you need — is the address.

How it works

  1. Send the IP

    Make one authenticated GET to /v1/ip/{ip} with your Bearer key. Works for any IPv4 or IPv6 address — the one you pulled from a request header, a signup record, or a log line. No request body, no batching ceremony.

  2. We resolve it against a compiled blob

    The lookup is a longest-prefix match against a single self-hosted dataset loaded in-memory at the edge — no third-party round trip. It merges ASN, geo, datacenter/proxy/VPN/Tor/scanner classification, and public threat blocklists (Spamhaus DROP, abuse.ch Feodo, blocklist.de, stamparm/ipsum, Team Cymru bogons) offline, so a typical lookup resolves in about a millisecond.

  3. You get a score, tags, and risk back

    The response groups everything: a 0-100 risk.score with a level band, network (asn, org), location (ISO country, plus city via ?include=city), type (datacenter/hosting/isp/mobile/cloud), and risk (anonymity + threat-blocklist flags), plus a flat flags array. Threshold on the score, or branch on the specific booleans you care about.

Quickstart

Send an authenticated GET to /v1/ip/{ip}. No body, no headers beyond Authorization.

bash
curl https://api.formshield.dev/v1/ip/185.220.101.1 \
  -H "Authorization: Bearer YOUR_API_KEY"

Every response is wrapped in the standard envelope — the answer in data, request info in metadata. This is a known Tor exit node, so it scores 100 / high and carries the full set of risk tags:

json
{
  "version": "1",
  "data": {
    "ip": "185.220.101.1",
    "ip_version": 4,
    "network": { "asn": 60729, "org": "TORSERVERS-NET" },
    "location": { "country": "DE", "city": null },
    "type": { "datacenter": true, "hosting": true, "isp": false, "mobile": false, "cloud": false },
    "risk": {
      "proxy": true, "vpn": true, "tor": true, "residential_proxy": false, "scanner": true,
      "spamhaus_drop": false, "feodo_c2": false, "blocklist_de": false, "bogon": false, "blocklist": false, "ipsum_level": 0,
      "score": 100, "level": "high",
      "factors": ["tor", "proxy", "vpn", "scanner", "datacenter", "hosting"]
    },
    "flags": ["datacenter", "hosting", "proxy", "vpn", "tor", "scanner"]
  },
  "error": null,
  "metadata": {
    "request_id": "req_04e3c0cc2ffd",
    "processing_time_ms": 1,
    "source": "ipatlas",
    "dataset": "ipatlas-v4-2026-06-08-bae459220a",
    "format_version": 2,
    "enrichment": { "requested": [], "resolved": [], "credits": 1 }
  }
}

The base lookup costs one credit and resolves in about a millisecond. The simplest integration thresholds on risk.score (e.g. >= 60 → challenge); for finer control, branch on the specific booleans — treat risk.tor or type.datacenter on a consumer signup as a reason to challenge or review. metadata.dataset is the exact dataset build that answered, so a verdict is always traceable.

The fields

Each lookup returns the same grouped shape. The two groups that matter most are type (what the network is) and risk (how the IP is being used).

network object path

The owning network: asn (autonomous system number) and org (the registered org name). A datacenter or cloud IP hitting a consumer signup form is rarely a real customer.

location object path

country — the ISO country code for the address. City-level fields (city, subdivision_name, latitude, longitude) populate only when you ask for ?include=city.

type object path

Five booleans describing the kind of network: datacenter, hosting, isp, mobile, cloud. They aren’t mutually exclusive — a hosting IP is usually datacenter too.

risk object path

The threat axis. A score (0-100, higher = riskier) and level band (none/low/medium/high) summarize everything; factors lists what moved the score (see Reputation score). The booleans break it down: anonymity/abuse tells — proxy, vpn, tor, residential_proxy, scanner — and threat-blocklist hits — spamhaus_drop (hijacked/spam netblock), feodo_c2 (active botnet command-and-control), blocklist_de (recently reported for abuse), bogon (unrouted/spoofed source), blocklist (multi-list consensus), plus ipsum_level (0-8, how many lists agree). These threat flags are positive-only: true is high-confidence, but false just means “not on our lists,” not “proven clean.”

flags string[] path

A flat array of every type and risk flag that is true (the booleans only — not the score/level/ipsum_level scalars). Convenient when you’d rather scan one list than read each boolean.

Type vs risk at a glance

GroupFieldsAnswers
typedatacenter, hosting, isp, mobile, cloudWhat kind of network owns this IP?
riskscore + level; proxy, vpn, tor, residential_proxy, scanner; spamhaus_drop, feodo_c2, blocklist_de, bogon, blocklist, ipsum_levelHow risky is this IP, and why?

An address can carry several of both at once, so branch on the specific booleans your use case cares about rather than assuming one excludes another.

Reputation score

risk.score is a single 0-100 number — 0 is clean, 100 is maximum risk — so you can gate on one threshold instead of reading a dozen booleans. It’s always present, costs no extra credit, and comes with a level band and a factors audit trail.

levelscoreTypical handling
none0Allow
low129Allow / log
medium3059Review or step-up
high60100Challenge or block

The score is additive and transparent: each signal contributes a weight, and factors names exactly what moved it, so you can see why an address scored the way it did. Threat-blocklist hits weigh heaviest (a spamhaus_drop or feodo_c2 alone lands in medium/high); anonymity tells (tor, proxy, residential_proxy) add moderate risk; a residential isp and icloud_relay subtract risk. A verified crawler (a bot block) cancels the datacenter penalty — a real Googlebot from a datacenter isn’t risky.

bash
curl https://api.formshield.dev/v1/ip/1.10.16.1 \
  -H "Authorization: Bearer YOUR_API_KEY"
json
{
  "data": {
    "ip": "1.10.16.1",
    "ip_version": 4,
    "location": { "country": null },
    "type": { "datacenter": false, "hosting": false, "isp": false, "mobile": false, "cloud": false },
    "risk": {
      "proxy": false, "vpn": false, "tor": false, "residential_proxy": false, "scanner": true,
      "spamhaus_drop": true, "feodo_c2": false, "blocklist_de": false, "bogon": false, "blocklist": false, "ipsum_level": 0,
      "score": 90, "level": "high",
      "factors": ["spamhaus_drop", "scanner"]
    },
    "flags": ["scanner", "spamhaus_drop"]
  }
}

Enrichment with ?include=

Default lookups stay lean and cheap. Opt into extra datasets with a comma-separated ?include= (or include=all). Unknown names return a 400.

datasetaddscost
regionnetwork.cloud_provider + network.cloud_region (e.g. aws / us-east-1)included
companya company block: { name, website, country, category, confidence } (by ASN)included
rdnsnetwork.hostname (reverse DNS / PTR) + network.hostname_verified (forward-confirmed)+1 credit on a cache miss
citylocation.city, subdivision_name, latitude, longitude (IPv4 only)included
bash
curl "https://api.formshield.dev/v1/ip/3.5.1.1?include=region,company,rdns" \
  -H "Authorization: Bearer YOUR_API_KEY"
json
{
  "data": {
    "ip": "3.5.1.1",
    "ip_version": 4,
    "network": {
      "asn": 16509,
      "org": "AMAZON-02",
      "hostname": "s3-us-east-1.amazonaws.com",
      "hostname_verified": true,
      "cloud_provider": "aws",
      "cloud_region": "us-east-1"
    },
    "company": {
      "name": "Amazon.com",
      "website": "https://www.amazon.com",
      "country": "US",
      "category": "Hosting and Cloud Provider",
      "confidence": "high"
    },
    "location": { "country": "US" },
    "type": { "datacenter": true, "hosting": true, "isp": false, "mobile": false, "cloud": true },
    "risk": {
      "proxy": false, "vpn": false, "tor": false, "residential_proxy": false, "scanner": false,
      "spamhaus_drop": false, "feodo_c2": false, "blocklist_de": false, "bogon": false, "blocklist": false, "ipsum_level": 0,
      "score": 23, "level": "low",
      "factors": ["datacenter", "hosting", "cloud"]
    },
    "flags": ["datacenter", "hosting", "cloud"]
  },
  "error": null,
  "metadata": {
    "source": "ipatlas",
    "enrichment": {
      "requested": ["region", "company", "rdns"],
      "resolved": ["region", "company", "rdns"],
      "cache": { "rdns": "hit" },
      "credits": 1
    }
  }
}

City geolocation

?include=city adds city, region, and coordinates to the location block:

bash
curl "https://api.formshield.dev/v1/ip/1.0.0.5?include=city" \
  -H "Authorization: Bearer YOUR_API_KEY"
json
{
  "data": {
    "ip": "1.0.0.5",
    "ip_version": 4,
    "location": {
      "city": "South Brisbane",
      "subdivision_name": "Queensland",
      "latitude": -27.4767,
      "longitude": 153.017,
      "country": "AU"
    }
  },
  "metadata": { "enrichment": { "requested": ["city"], "resolved": ["city"], "credits": 1 } }
}

Known bots

When the address falls inside a verified crawler’s published IP range, the response carries a bot block — independent of the User-Agent. This identifies a crawler hitting you with no UA, or one forging a browser UA, from the IP alone. No bot key means the address isn’t in a known crawler range (it may still be a bot the UA reveals — that’s a separate check).

bash
curl https://api.formshield.dev/v1/ip/66.249.66.1 \
  -H "Authorization: Bearer YOUR_API_KEY"
json
{
  "data": {
    "ip": "66.249.66.1",
    "network": { "asn": 15169, "org": "GOOGLE" },
    "type": { "datacenter": true, "hosting": false, "isp": false, "mobile": false, "cloud": false },
    "bot": {
      "is_known_bot": true,
      "operator": "Google",
      "name": "Googlebot",
      "id": "googlebot",
      "verified_method": "published_range"
    }
  }
}
bot object path

Present only when the IP is in a known crawler range. operator is the company (e.g. Google, OpenAI, Amazon), name + id are the crawler, and verified_method is how the IP was matched (published_range). Covers Googlebot, Bingbot, GPTBot, Applebot, Amazonbot, DuckDuckBot, and more — IPv4 and IPv6.

IPv6

IPv6 resolves through the same endpoint, envelope, and field shape as IPv4 — the only difference is ip_version: 6. IPv6 is classified at /64 granularity (the standard single-network boundary): reputation is constant within a /64, so the host bits don’t change the answer. ?include= works the same; company resolves by ASN, so it’s populated for IPv6 too.

bash
curl "https://api.formshield.dev/v1/ip/2606:4700:4700::1111?include=company" \
  -H "Authorization: Bearer YOUR_API_KEY"
json
{
  "data": {
    "ip": "2606:4700:4700::1111",
    "ip_version": 6,
    "network": { "asn": 13335, "org": "CLOUDFLARENET", "cloud_provider": null, "cloud_region": null },
    "company": { "name": "Cloudflare", "website": "https://www.cloudflare.com", "country": "US", "category": "Hosting and Cloud Provider", "confidence": "high" },
    "location": { "country": "US", "city": null },
    "type": { "datacenter": true, "hosting": true, "isp": false, "mobile": false, "cloud": true },
    "risk": {
      "proxy": false, "vpn": false, "tor": false, "residential_proxy": false, "scanner": false,
      "spamhaus_drop": false, "feodo_c2": false, "blocklist_de": false, "bogon": false, "blocklist": false, "ipsum_level": 0,
      "score": 23, "level": "low",
      "factors": ["datacenter", "hosting", "cloud"]
    },
    "flags": ["datacenter", "hosting", "cloud"]
  },
  "error": null,
  "metadata": { "source": "ipatlas", "enrichment": { "requested": ["company"], "resolved": ["company"], "credits": 1 } }
}

A v4-mapped address (::ffff:a.b.c.d) is folded to its IPv4 form: the response echoes the dotted quad with ip_version: 4.

Errors

A non-2xx populates error (and data is null); these responses are not billed. The message text is human-readable but not stable — branch on error.code.

Statuserror.codeWhenWhat to do
400VALIDATION_ERRORThe path isn’t a valid IPv4/IPv6 address, or ?include= names an unknown datasetFix the address; use only region, company, rdns, city, or all
422UNSUPPORTEDA reserved / non-global address — private, loopback, link-local, ULA (fc00::/7), or multicast — which carries no public intelligenceLook up a public, routable address
404NOT_FOUNDThe address is valid but unclassified (an unallocated IPv6 /64, or the dataset isn’t loaded)Nothing to fix — there’s simply no record
json
{
  "version": "1",
  "data": null,
  "error": { "code": "VALIDATION_ERROR", "message": "unknown include dataset(s): bogus" },
  "metadata": { "request_id": "req_8f2a1c", "processing_time_ms": 0 }
}

Common questions

How do I check an IP’s reputation with the API?

Send an authenticated GET to https://api.formshield.dev/v1/ip/{ip} with an Authorization: Bearer header. You get back a 0-100 risk.score with a level band, the IP’s network class (datacenter, hosting, isp, mobile, cloud), its risk tags (proxy, vpn, tor, residential_proxy, scanner) and threat-blocklist hits (Spamhaus DROP, botnet C2, abuse reporters, bogon), ASN, org, and ISO country — in one response. No request body is needed, and each lookup costs one credit.

What is the risk.score, and how should I use it?

It’s a single 0-100 number (0 = clean, 100 = max risk) so you can gate on one threshold instead of reading every boolean. Branch on the level band for stability — none/low → allow, medium → review, high → challenge or block — and read factors to see which signals drove it. The score is included on every lookup at no extra credit. See Reputation score.

What’s the difference between the type flags and the risk flags?

The type block describes what kind of network owns the IP — datacenter, hosting provider, ISP, mobile carrier, or cloud. The risk block scores and flags how the IP is being used in ways that correlate with abuse: anonymity tells (proxy, VPN, Tor, residential proxy, scanner) and threat-blocklist hits (Spamhaus DROP, botnet C2, abuse reporters, bogon). An address can carry several of both at once, so branch on the specific booleans your use case cares about — or just threshold the score.

How much does each lookup cost, and what costs extra?

A base lookup is one credit, and it includes the risk.score. The region, company, and city enrichments add no extra credit. The only add-on that can cost more is rdns (reverse DNS), which adds one credit only when the hostname isn’t already cached. Every response reports the exact credits charged under metadata.enrichment.

Does it support IPv6?

Yes. GET /v1/ip/{ip} resolves IPv6 with the same envelope, score, and fields as IPv4 (ip_version: 6), classified at /64 granularity. ?include=region,company,rdns works for IPv6 too — company is keyed by ASN, and reverse DNS uses ip6.arpa. The one exception is ?include=city, which is IPv4-only for now (city fields stay null for IPv6).

Next steps

Type to search…

↑↓ navigate open esc close