This release is one idea applied in both directions: more signals, fewer false positives. We added three signals that catch more abuse, three that stop you from flagging real people, and two of plain network context. Every one of them is on the free tier, and every one is still just an input — not a verdict.
Three signals that catch more abuse
is_drop_listed— the IP is on the Spamhaus DROP list: a do-not-route range of known hostile networks. It adds +40 to the risk score.is_bogon— the IP is unallocated or reserved space that should never source real traffic (+30).network.rpki— route-origin validation, reported asvalid/invalid/unknownfrom public BGP and RPKI data. Aninvalidorigin adds +20.
Three signals that reduce false positives
Here's the failure mode we wanted to kill. Apple iCloud Private Relay, Starlink and public DNS resolvers all exit from ranges that look like hosting. Score them as a datacenter and you punish real users for using a privacy feature or a satellite link. So we now recognise these benign network kinds explicitly:
is_relay(withrelay_provider) — a privacy-relay exit, e.g. Apple iCloud Private Relay.connection_type === "satellite"— a satellite link, e.g. Starlink.is_public_resolver— a public DNS resolver such as 8.8.8.8 or 1.1.1.1.
And the part that makes them matter: the benign-network suppressor. When any of those is true, the risk score is capped at 20 and benign_network_kind is added to reasons[]. It's a cap, not a negative weight — the score never drops below what the other signals produced; it just can't exceed 20 for a benign kind. You still see every signal that fired.
const r = await geoq.check(ip);
// Apple iCloud Private Relay exits from hosting-style ranges, but the people
// behind them are ordinary users. is_relay is a benign network kind.
if (r.signals.is_relay) {
console.log('relay:', r.signals.relay_provider); // e.g. 'icloud'
}
// The score already reflects this: a relay (or satellite, or public resolver)
// is capped at 20 and tagged so you can see why.
// r.risk -> { score: 20, level: 'low', reasons: ['connection_type:datacenter', 'benign_network_kind'] } Full detail in the risk-score methodology.
Two signals of network context
Routing health (network.is_announced) and RIR allocation (allocation_date, allocation_age_days, registration_country, from the registries' published delegated statistics). These aren't scored on their own — they're context to reason with. Freshly-allocated space, for instance, is often worth a second look.
One breaking change: is_datacenter → connection_type
To make room for satellite, the boolean is_datacenter is gone. A datacenter IP is now connection_type === "datacenter", and the evidence key is evidence.connection_type:
// Before (<= 0.7.x)
if (r.signals.is_datacenter) { /* ... */ }
// After (0.8.0)
if (r.signals.connection_type === 'datacenter') { /* ... */ }
// connection_type also reports 'satellite' (e.g. Starlink) and 'unknown'. If you use ?format=ipinfo, that shim keeps working: privacy.hosting now derives from connection_type, and privacy.relay maps to the real is_relay signal instead of the constant false it used to return. See the migration mode docs.
An honest note
One of the eight, recent_abuse (from Emerging Threats / CINS), is beta and carries zero weight today — we surface it so you can read it, not yet as a contributor to the score. As always: GeoQ signals are probabilistic, and none of them should be the sole basis of an automated decision about a person. See the acceptable use policy.
All eight signals — abuse signals and false-positive reducers alike — are on the free tier, 5,000 lookups/day, no card. Get a key and the full schema is here.