API reference
A small REST API over HTTPS. Every response is JSON; every error carries a stable code.
Authentication
Send your key as a bearer token. X-API-Key is accepted too, for clients that cannot set an Authorization header.
curl https://geopass.site/api/v1/lookup/1.1.1.1 \
-H "Authorization: Bearer gp_live_…"
# equivalent
curl https://geopass.site/api/v1/lookup/1.1.1.1 \
-H "X-API-Key: gp_live_…"Keys are stored hashed and shown once at creation. Revoking a key takes effect on the next request.
Endpoints
https://geopass.site/ api/ v1/ lookup/ {ip}Resolve one address.https://geopass.site/ api/ v1/ lookup/ meResolve the caller’s address.https://geopass.site/ api/ v1/ lookup/ batchResolve many at once.https://geopass.site/ api/ v1/ usageCheck what is left. Free.A batch takes a JSON body and bills one lookup per valid address:
curl -X POST https://geopass.site/api/v1/lookup/batch \
-H "Authorization: Bearer gp_live_…" \
-H "Content-Type: application/json" \
-d '{"ips": ["1.1.1.1", "8.8.8.8"]}'Checking your usage
/v1/usage reports where you stand without drawing on the allowance, so a client can poll it or show a meter without spending a lookup to ask.
curl https://geopass.site/api/v1/usage -H "Authorization: Bearer gp_live_…"
{
"plan": { "id": "pro", "name": "Pro" },
"quota": {
"limit": 50000,
"used": 1234,
"remaining": 48766,
"resetsAt": "2026-10-17T14:32:05.000Z"
},
"extraLookups": { // bought packs; not reset with the allowance
"remaining": 24310,
"packs": [ // soonest expiry first — the order they are spent
{ "lookups": 25000, "remaining": 20000, "expiresAt": "2026-09-17T00:00:00.000Z" },
{ "lookups": 25000, "remaining": 4310, "expiresAt": "2027-08-11T00:00:00.000Z" }
]
},
"batchLimit": 100
}Response
meta.sources names the upstream behind each half of the answer, and meta.cached tells you whether it was served from our cache. A null source means nothing answered that half, so its fields are defaults rather than findings.
On the privacy block: hosting and anonymous are reported by our upstream. vpn, proxy, tor and relay are not yet broken out and stay false; use anonymous as the signal that an origin is being hidden.
Fields a source does not supply come back null, never invented. Booleans that were never checked are null too, so false always means an actual answer.
{
"ip": "1.1.1.1",
"type": "ipv4",
"location": {
"country": "AU", // ISO 3166-1 alpha-2
"countryName": "Australia",
"region": "Queensland",
"regionCode": "QLD",
"city": "Brisbane",
"postalCode": "9010",
"latitude": -27.4679,
"longitude": 153.0281,
"timezone": "Australia/Brisbane",
"continent": "Oceania",
"continentCode": "OC",
"publicPlace": false // airport, hotel, campus
},
"network": {
"asn": 13335,
"organization": "Cloudflare, Inc.",
"isp": "Cloudflare, Inc.",
"domain": "cloudflare.com",
"hostname": "one.one.one.one",
"route": null,
"type": "hosting", // hosting, isp, business, education
"mobile": false, // cellular carrier rather than fixed line
"satellite": false,
"anycast": true // announced from many locations
},
"privacy": {
"hosting": true, // datacentre address
"anonymous": false, // origin is obscured
"proxy": false,
"vpn": false,
"tor": false,
"relay": false
},
"meta": {
"sources": { "geo": "ipinfo:core", "privacy": "ipinfo:core" },
"cached": { "geo": true, "privacy": true }
}
}Quota
There is no per-second rate limit — send requests as fast as you can. The monthly allowance is the only ceiling, and these headers ride along on every successful response so a client can watch it drain rather than discover it at a 429.
x-quota-limit: 10000
x-quota-remaining: 9999
x-quota-reset: 1792247525x-quota-reset is when the monthly allowance refills, as a Unix timestamp in seconds. Watch x-quota-remaining and alert yourself well before it reaches zero — it is the cheapest warning you will get, and it costs nothing to read. /v1/usage answers the same question without making a lookup, if you would rather poll.
Once the monthly allowance is spent, lookups come out of any extra packs on the account, and a further header appears saying what is left of them:
x-quota-remaining: 0
x-quota-topup-remaining: 24310Packs are bought from the billing page, are drawn on only after the plan’s own allowance runs out, and — unlike the monthly allowance — carry over when it resets. Each expires twelve months after purchase.
Read the expiry from extraLookups.packs rather than the total. Two accounts with 24,310 extra lookups can be in very different places: one with all of it next year, the other with 20,000 of it gone tomorrow. Packs are spent in the order listed, so whatever expires first is used first.
| Plan | Monthly | Batch |
|---|---|---|
| Trial | 500 | 10 |
| Starter | 10,000 | 25 |
| Pro | 50,000 | 100 |
| Business | 100,000 | 500 |
Each allowance lasts a month and renews on your billing date — the day of the month your subscription started, when you are charged. An account without a subscription renews on the day of the month it was created. Quotas are shared across every key on the account.
A new account starts on Trial, which is there to let you check the data against addresses you already know before paying for anything. It is not sized for production traffic.
Errors
{ "error": { "code": "reserved_address", "message": "…" } }| Status | Code | When |
|---|---|---|
| 400 | invalid_request | The address could not be parsed, or the batch body was malformed. |
| 401 | unauthorized | The API key is missing, malformed, revoked, or unknown. |
| 422 | reserved_address | A private, loopback, link-local or documentation address. |
| 429 | quota_exceeded | The monthly allowance and any extra packs are spent. Retry-After gives the reset time. |
| 502 | upstream_unavailable | No provider could resolve the address. Not billed. |
Nothing you cannot use is billed. Malformed and reserved addresses never draw on the quota, and a lookup that fails upstream is refunded.