Documentation
One API key. Two endpoints — pick LITE for geo + ASN, MAX for threat signals. Quota for each bucket is tracked independently.
Copy a self-contained Markdown spec — paste into Claude, ChatGPT, Cursor, or Copilot and ask it to build the client.
Quick start
- Sign up for a free account — Hobby is free, no credit card.
- Pick a plan on the pricing page. MAX bucket requires Plus or higher.
- Open your dashboard and create an API key.
- Make a request:
# LITE — fast geo + ASN
curl -H "Authorization: Bearer LOOKIP_KEY" \
https://api.lookip.io/v1/lookup/lite/8.8.8.8# MAX — threat signals + city-level geo
curl -H "Authorization: Bearer LOOKIP_KEY" \
https://api.lookip.io/v1/lookup/max/8.8.8.8The response JSON shape is stable per bucket — write your client once and it works across every plan.
Authentication
The API accepts two authentication schemes. Bearer tokens are preferred; the query parameter form exists for environments that cannot set custom headers (e.g. simple image proxies, webhooks).
# Preferred
Authorization: Bearer LOOKIP_KEY
# Fallback (URL form)
GET /v1/lookup/max/8.8.8.8?token=LOOKIP_KEYKeys are server-side only. Never embed them in client-side JavaScript, mobile apps, or public repositories. Rotate keys from the dashboard if a leak is suspected — revocation is instant.
LITE vs MAX
Every plan ships two separate quotas. The two buckets are billed independently — burning LITE volume never blocks MAX.
Geo + ASN. Sub-5ms p95, no per-request API cost. Use it when you only need country / network ownership.
- Country, continent, countryCode
- ASN, organization, domain
- IPv4 + IPv6
Adds city-level geo, reverse DNS, threat signals, hosting flags, and mobile carrier data.
- City, region, postal, lat/long, timezone, DMA, geoname
- VPN / proxy / Tor / relay / residential-proxy + service name
- Anonymous, anycast, hosting, mobile, satellite, residential-proxy flags
- Mobile carrier (MCC / MNC)
- Hostname (reverse DNS)
The bucket is picked by the URL path, not a header or body field — so the same caller can mix and match in the same day.
Endpoints
All endpoints live under /v1. Production base URL: https://api.lookip.io.
| Method | Path | Bucket | Description |
|---|---|---|---|
GET | /v1/lookup/lite/:ip | LITE | Fast geo + ASN. Bills the LITE bucket. |
POST | /v1/lookup/lite | LITE | Same as GET but accepts an optional context body. |
GET | /v1/lookup/max/:ip | MAX | City, threats, hostname, carrier. Bills the MAX bucket. |
POST | /v1/lookup/max | MAX | Same as GET but accepts an optional context body. |
POST | /v1/batch/lite | LITE | Up to 100 IPs in one call against the LITE bucket. |
POST | /v1/batch/max | MAX | Up to 100 IPs in one call against the MAX bucket. |
GET | /v1/lookup/me | MAX | Look up the caller’s own IP (MAX bucket). |
Example batch request:
# Batch lookups against the MAX bucket (use /v1/batch/lite for LITE).
curl -X POST https://api.lookip.io/v1/batch/max \
-H "Authorization: Bearer LOOKIP_KEY" \
-H "Content-Type: application/json" \
-d '{ "ips": ["8.8.8.8", "1.1.1.1", "2606:4700:4700::1111"] }'Every response includes X-Plan, X-Lookup-Kind, X-Quota-Limit, and X-Quota-Used headers so your client can track bucket usage without an extra round-trip.
Response shape
The response JSON is stable per bucket. LITE returns location + network; MAX adds threats, flags, mobile, and richer location/network fields.
{
"ip": "8.8.8.8",
"location": {
"country": "United States",
"countryCode": "US",
"continent": "North America",
"continentCode": "NA"
},
"network": {
"asn": "AS15169",
"organization": "Google LLC",
"domain": "google.com"
},
"lastUpdated": "2026-05-14T03:21:00Z",
"datasetUpdatedAt": "2026-05-13"
}{
"ip": "8.8.8.8",
"hostname": "dns.google",
"location": {
"city": "Mountain View",
"region": "California",
"regionCode": "CA",
"country": "United States",
"countryCode": "US",
"continent": "North America",
"continentCode": "NA",
"latitude": 37.4056,
"longitude": -122.0775,
"timezone": "America/Los_Angeles",
"postalCode": "94043",
"dmaCode": "807",
"geonameId": "5375480",
"accuracyRadiusKm": 50,
"geoUpdatedAt": "2026-01-04"
},
"network": {
"asn": "AS15169",
"organization": "Google LLC",
"domain": "google.com",
"type": "hosting",
"asnUpdatedAt": "2021-05-01"
},
"threats": {
"service": "NordVPN",
"lastSeen": "2026-05-10",
"recentActivityPct": 85,
"isProxy": false,
"isRelay": false,
"isTor": false,
"isVpn": false,
"isResidentialProxy": false
},
"flags": {
"isAnonymous": false,
"isAnycast": true,
"isHosting": true,
"isMobile": false,
"isSatellite": false,
"isResidentialProxy": false
},
"mobile": { "carrier": "T-Mobile", "mcc": "310", "mnc": "260" },
"lastUpdated": "2026-05-14T03:21:00Z"
}Optional context
Both POST /v1/lookup/lite and POST /v1/lookup/max accept an optional context object alongside the IP. These fields let you tag a request with surrounding session information so you can slice your usage logs by user, campaign, or workflow step inside the dashboard.
Context never leaves Lookip. It is stored only against your account's request log and is never forwarded to any third party.
curl -X POST https://api.lookip.io/v1/lookup/max \
-H "Authorization: Bearer LOOKIP_KEY" \
-H "Content-Type: application/json" \
-d '{
"ip": "8.8.8.8",
"context": {
"userAgent": "Mozilla/5.0 …",
"email": "[email protected]",
"username": "alice",
"note": "checkout #4821",
"tags": ["signup", "trial"]
}
}'| Field | Purpose |
|---|---|
userAgent | Originating UA string of the end user. |
email | End-user email for log correlation. |
username | Your internal username / handle. |
firstName / lastName | Personal name fields, if you collect them. |
phone | E.164 phone number. |
address, city, region, country, postal | Claimed billing/shipping address — useful for fraud checks against IP geo. |
note | Free-form short string (e.g. order id, ticket). |
tags | Array of labels for filtering the dashboard. |
extra | Open JSON object for anything else you need to attach. |
Errors
Errors are returned as JSON with an error.code and a human-readable error.message. The HTTP status reflects the category. Quota errors also include the bucket so you can route retries.
| Code | HTTP | Meaning |
|---|---|---|
invalid_ip | 400 | The supplied IP is not a valid IPv4 or IPv6 address. |
unauthorized | 401 | Missing, invalid, or revoked API key. |
no_active_plan | 402 | The account has no active subscription. Pick a plan to continue. |
bucket_disabled | 402 | Your plan does not include the requested bucket. Hobby has LITE only — upgrade to Plus or higher for MAX. |
quota_exhausted | 429 | The bucket's monthly allowance is used. The other bucket continues to work. The response body includes kind. |
rate_limited | 429 | Per-second cap exceeded. Back off and retry with jitter. |
lite_db_unavailable | 503 | LITE database not loaded yet (cold-start, <30s). Retry — no quota burned. |
upstream_error | 5xx | Transient server failure on a MAX lookup. Retry with backoff. |
Rate limits & quotas
- Monthly bucket quotas. LITE and MAX are counted separately. Resets at 00:00 UTC on your subscription anchor day.
- Per-second soft cap. ~50 req/s per key across both buckets. Exceeding it returns
rate_limited— retry after a short backoff. - Batch. Each IP inside a batch request counts as one lookup against the matching bucket.
The X-Lookup-Kind, X-Quota-Limit, and X-Quota-Used response headers carry live bucket usage so you can switch buckets gracefully before hitting 429.
SDKs & examples
The API is plain HTTP + JSON, so any client works. Here are minimal examples:
# MAX — threat signals + city-level geo
curl -H "Authorization: Bearer LOOKIP_KEY" \
https://api.lookip.io/v1/lookup/max/8.8.8.8// Use /lite when you only need geo + ASN, /max for threat signals.
const r = await fetch("https://api.lookip.io/v1/lookup/max/8.8.8.8", {
headers: { Authorization: `Bearer ${process.env.LOOKIP_KEY}` },
});
if (!r.ok) throw new Error(`lookup failed: ${r.status}`);
const data = await r.json();
console.log(data.location?.country, data.threats?.isVpn);import os, requests
# Pick the bucket explicitly via the URL path.
r = requests.get(
"https://api.lookip.io/v1/lookup/max/8.8.8.8",
headers={"Authorization": f"Bearer {os.environ['LOOKIP_KEY']}"},
timeout=5,
)
r.raise_for_status()
print(r.json())Need a typed client? The response shape per bucket is stable; copy it into your codebase as a Zod schema or TypeScript interface and you're done.