Blog

More signals, fewer false positives

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 as valid / invalid / unknown from public BGP and RPKI data. An invalid origin 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 (with relay_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_datacenterconnection_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.

Signals are probabilistic, not facts. Don't make a sole-basis automated decision about a person — see the acceptable use policy.

Keep reading

Get a free key — 5,000 lookups/day, no card.

Every signal and the same risk score as every paid plan. Upgrade only when you outgrow it.