Docs
Migration mode
Migrating from IPinfo? Add ?format=ipinfo to a /v1/check request and GeoQ returns an IPinfo-shaped response. Your existing parsing code keeps working while you point the base URL at GeoQ — switch first, refactor later.
This is a migration aid, not the recommended format. The compatibility shape can't carry GeoQ's native extras — there's noreasons[]and no per-signalevidencehere. Once you've switched, dropformat=ipinfoand read the native response so you can audit why a score fired. It's the only vendor compatibility format we ship.
Request
Same endpoint and auth as a normal lookup — add one query parameter:
| Parameter | Value |
|---|---|
format | ipinfo — return the IPinfo-compatible shape. Omit it (or any other value) for the native GeoQ response. |
$ curl "https://api.geoq.io/v1/check?ip=8.8.8.8&format=ipinfo" \ -H "x-api-key: $GEOQ_API_KEY"
Response
A flat object that mirrors IPinfo's full-detail response, including a privacy object. GeoQ's own extras live under a vendor-prefixed geoq key, so an existing IPinfo parser ignores them:
{ "ip": "8.8.8.8", "city": "Mountain View", "region": "California", "country": "US", "loc": "37.4,-122.1", "org": "AS15169 Google LLC", "timezone": "America/Los_Angeles", "privacy": { "vpn": false, "proxy": false, "tor": false, "relay": false, "hosting": true, "service": "gcp" }, "geoq": { "risk_score": 35, "attribution": "https://geoq.io/attributions", "note": "Migration mode. Drop format=ipinfo for risk.reasons[] + evidence{}." } }
Fields
| Field | Type | Description |
|---|---|---|
ip | string | The IP that was checked. |
city | string|null | From geo.city. |
region | string|null | From geo.region. |
country | string|null | ISO 3166-1 alpha-2 code, from geo.country_code (note: this is the code, not the full name). |
loc | string|null | "lat,lng" string, composed from geo.latitude + geo.longitude. |
org | string|null | "AS<n> <org>" string, composed from network.asn + network.as_org. |
timezone | string|null | From geo.timezone. |
privacy.vpn | boolean | From signals.is_vpn. |
privacy.proxy | boolean | From signals.is_proxy (residential-proxy detection is beta). |
privacy.tor | boolean | From signals.is_tor. |
privacy.relay | boolean | From signals.is_relay (e.g. Apple iCloud Private Relay). |
privacy.hosting | boolean | True when signals.connection_type === "datacenter". |
privacy.service | string | From signals.relay_provider, else signals.datacenter_provider (e.g. "gcp"), else empty string. |
geoq.risk_score | number | GeoQ's 0–100 risk.score, surfaced under a vendor-prefixed key. |
geoq.attribution | string | Attribution URL (honour on the Free tier). |
geoq.note | string | A reminder that this is migration mode. |
The privacy mapping
The privacy object maps directly to GeoQ signals: vpn → is_vpn, proxy → is_proxy, tor → is_tor, relay → is_relay, and hosting is true when connection_type === "datacenter". service carries the relay provider (e.g. icloud) when the IP is a relay, otherwise the datacenter provider code. (Earlier versions hardcoded relay: false; GeoQ now has a real relay signal, so it maps through.)
What you give up in this mode
- No
risk.reasons[]— you only get the numericgeoq.risk_score, not the signals that produced it. - No per-signal
evidencelabels (authoritative/inferred/beta). - No verified-crawler fields (
is_verified_bot,verified_bot_name). countryis the ISO code only; the native response also gives you the full country name.
For all of those, switch to the native format. See the full IPinfo migration guide for field-by-field mapping and before/after code.