API reference
Endpoint
GET https://api.instantgeo.info/v1/geoGET / is an alias. There is no POST.
Authentication
The key may be supplied three ways, checked in this order:
| Where | Example | Notes |
|---|---|---|
| Query string | ?key=ig_pk_… | Recommended for browsers, no preflight |
| Bearer token | Authorization: Bearer ig_sk_… | Triggers a CORS preflight |
| Header | X-API-Key: ig_sk_… | Triggers a CORS preflight |
Requests with no key are served at the free tier with a hard limit of 60 per hour per IP.
Parameters
| Parameter | Description |
|---|---|
key | Your publishable or secret key |
fields | Comma-separated list to trim the response. Narrows what your plan allows; never widens it. Unknown names are ignored. |
ip | Ask about an address other than the caller’s. Secret keys only. See below. |
Response
{ "ip": "203.0.113.7", "country": "GB", "countryName": "United Kingdom", "continent": "EU", "isEUCountry": false, "region": "England", "regionCode": "ENG", "city": "Bristol", "postalCode": "BS1", "latitude": "51.4536", "longitude": "-2.5975", "timezone": "Europe/London", "asn": 64496, "asOrganization": "EXAMPLE ISP", "currency": "GBP", "languages": ["en-GB"], "metroCode": null}| Field | Type | Plan | Notes |
|---|---|---|---|
ip | string | Paid | The caller’s IP. Never stored by us. |
country | string | Free | ISO 3166-1 alpha-2 |
countryName | string | Free | English name |
continent | string | Free | AF AN AS EU NA OC SA |
isEUCountry | boolean | Paid | EU membership, post-Brexit |
region | string | Paid | First-level subdivision |
regionCode | string | Paid | Subdivision code |
city | string | Paid | |
timezone | string | Paid | IANA identifier |
postalCode | string | Paid | Frequently null outside NA and Europe |
latitude | string | Paid | City-level, not device-level |
longitude | string | Paid | |
asn | number | Paid | Autonomous system number |
asOrganization | string | Paid | Network operator name |
currency | string | Paid | ISO 4217, derived from country |
languages | string[] | Paid | Primary languages, derived from country |
metroCode | string | Paid | US only |
partial | boolean | n/a | Present and true when geo data was incomplete |
Every field can be null. A field your plan does not include is absent
rather than null, so you can tell “not on your plan” apart from “we could not
determine it”.
Looking up another address
By default you get the location of whoever made the request. Pass ip= to ask
about a different address instead:
curl "https://api.instantgeo.info/v1/geo?key=ig_sk_...&ip=8.8.8.8"Secret keys only. A publishable key sits in your page source where anybody
can copy it. If it accepted ip=, anyone could lift it and use your account as
a free lookup service, billed to you. The origin allowlist cannot prevent that,
because a script calling us from a server sends no Origin at all.
A lookup counts as one request against your quota, the same as any other.
regionCode, postalCode and timezone are always null through ip=.
Not sometimes: the lookup database does not carry them at all, for any address.
Every other field is populated normally. If your plan includes those three and
you need them, they are only available on the caller path, where they come from
the network rather than from a database.
Three more things worth knowing before you build on it:
- It is less accurate than the caller path. Answering “where is the caller” uses network-level data that is not available for an arbitrary address. A lookup uses a database instead, and the two can disagree about the same address. If you can ask the question from the visitor’s own browser, that answer is the better one.
- Unknown addresses are not errors. Reserved ranges, recent allocations and
some mobile carriers are genuinely absent from the database. You get a
200with null fields and"partial": true, which says “we do not know” rather than “your address is wrong”. ipin the response echoes what you asked about, not the caller.
Accuracy
Coordinates are city-level, derived from the IP address. They are not GPS and should not be treated as a device location. A VPN, a corporate proxy, or a mobile carrier’s routing will place a caller somewhere they are not.
partial
When we cannot fully place an IP, you get a 200 with null fields and
"partial": true. We do not return a 500 for missing geolocation. A page that
breaks because a visitor is on an unusual network is worse than a page that
falls back to a default.
Response headers
| Header | Meaning |
|---|---|
Cache-Control | private, max-age=300, so please cache it |
X-RateLimit-Limit | Requests included this period |
X-RateLimit-Remaining | Requests left |
X-RateLimit-Reset | Unix seconds until the period resets |
Errors
Every non-2xx response has this shape:
{ "error": { "code": "origin_not_allowed", "message": "Origin \"https://evil.com\" is not allowed for key ig_pk_3lSd…. Add it at …", "docs": "https://instantgeo.info/docs/errors/origin_not_allowed" }}The message is written to be shown to a developer and acted on directly. The
docs link goes to a page explaining that specific code.
| Status | Code | |
|---|---|---|
| 401 | missing_key | No key supplied |
| 401 | malformed_key | Wrong shape |
| 401 | bad_signature | Not issued by us |
| 401 | unknown_key | No active record |
| 401 | revoked_key | Explicitly revoked |
| 403 | origin_not_allowed | Origin not on the allowlist |
| 403 | ip_lookup_not_allowed | ip= sent with a publishable key |
| 403 | invalid_ip | ip= was not a valid address |
| 403 | ip_lookup_unavailable | Lookups are not enabled here |
| 429 | rate_limited | Per-IP hourly limit |
| 429 | quota_exceeded | Monthly included requests used |
| 404 | not_found | No such endpoint |
| 500 | internal_error | Our fault |
CORS
OPTIONS is answered immediately with Access-Control-Max-Age: 86400.
CORS headers are present on error responses too, so a 403 is readable by the calling page rather than showing up as an opaque network failure.
Putting the key in the query string avoids the preflight entirely. See caching for why that matters more than it usually would.
Privacy
We do not store the caller’s IP address. It appears in the response body and
nowhere else: not in a log line, not in a table. That is enforced by a check in
our CI, not just a policy. The same is true of an address you pass to ip=.
Attribution
Lookups through ip= use the DB-IP Lite City database, made available by
DB-IP under the
Creative Commons Attribution 4.0 licence.
It is not used for the caller path.