IP lookup
POST /ip-lookup: where up to 100 IP addresses are, who runs their networks, and whether each is a VPN, proxy, Tor exit or on a block list. The request, an example answer and every field.
POST https://service.cerberius.com/api/ip-lookup
Where each IP address is, who runs its network, and how risky it looks. Use it to spot risky sign-ups and logins, show local prices or times, or block abuse.
Request#
Headers
X-API-Key: your key. A key made with Sign every call also needsX-TimestampandX-Signature(see Authentication and signed calls).Content-Type: application/json
Body
{ "data": ["8.8.8.8", "192.168.1.20"] }
| Field | Rules |
|---|---|
datalist of text | 1 to 100 IP addresses, IPv4 or IPv6. Spaces around each address are removed. |
- Every item must be a valid IP address. If one isn't, the whole call fails, so check them before you send them.
- Private and reserved addresses are allowed, but not looked up. These are addresses used only inside a home or office network (such as
10.x.x.xand192.168.x.x), loopback (127.0.0.1,::1), shared carrier networks (100.64.x.xto100.127.x.x) and the ranges kept for documentation (such as203.0.113.x). - The same address twice gets two results.
Example#
curl https://service.cerberius.com/api/ip-lookup \
-H "X-API-Key: $CERBERIUS_API_KEY" \
-H "Content-Type: application/json" \
-d '{"data": ["8.8.8.8", "192.168.1.20"]}'
The answer. Values like these change over time, so yours may differ.
{
"excess_charges_apply": false,
"data": [
{
"ip_address": "192.168.1.20",
"lookup_status": "private_not_queried",
"asn": "",
"isp": "",
"city": "",
"country": "",
"country_code": "",
"timezone": "",
"timezone_offset": 0,
"locale": "",
"currency": "",
"currencysymbol": "",
"in_eu": false,
"latitude": "",
"longitude": "",
"continet_code": "",
"continet_name": "",
"reverse_dns": "",
"is_tor_exit_point": false,
"is_anonimous": false,
"on_block_list": false,
"on_drop_list": false,
"recent_spam_domain": false,
"fraud_score": "",
"org_name": "",
"org_address": "",
"org_phone": "",
"org_email": "",
"abuse_email": "",
"remark": ""
},
{
"ip_address": "8.8.8.8",
"lookup_status": "Success",
"asn": "15169",
"isp": "Google LLC",
"city": "Mountain View",
"country": "United States",
"country_code": "US",
"timezone": "America/Los_Angeles",
"timezone_offset": -8,
"locale": "en-US",
"currency": "USD",
"currencysymbol": "$",
"in_eu": false,
"latitude": "37.405990",
"longitude": "-122.078514",
"continet_code": "NA",
"continet_name": "North America",
"reverse_dns": "dns.google.",
"is_tor_exit_point": false,
"is_anonimous": false,
"on_block_list": false,
"on_drop_list": false,
"recent_spam_domain": false,
"fraud_score": "low",
"org_name": "Google LLC",
"org_address": "1600 Amphitheatre Parkway",
"org_phone": "+1-650-253-0000",
"org_email": "[email protected]",
"abuse_email": "[email protected]",
"remark": ""
}
]
}
Results can come back in a different order
Addresses are looked up at the same time, and each result is added when it's ready. In the example, the private address came back first. Match each result to your list by its ip_address.
Every result has every field. When something isn't known, a text field is empty (""), a true/false field is false, and timezone_offset is 0.
Fields#
The answer#
| Field | What it means |
|---|---|
excess_charges_applytrue/false | Always false. There are no extra charges. Kept so code written for the first version keeps working. |
datalist | One result for each address you sent. |
Status#
| Field | What it means |
|---|---|
ip_addresstext | The address you sent, without spaces around it. |
lookup_statustext | Success: we found where the address is, or who runs its network. No information found: we found neither. private_not_queried: a private or reserved address, which isn't looked up. All its other fields are empty. |
Network and owner#
| Field | What it means |
|---|---|
asntext | The number of the network the address belongs to (its autonomous system number), digits only, such as 15169. 0 or empty when unknown. |
isptext | Who runs that network, such as Google LLC. |
org_nametext | Who the address is registered to, from its public registration record (WHOIS). |
org_addresstext | The street address in that record, when it has one. |
org_phonetext | The phone number in that record. |
org_emailtext | The contact email in that record. |
abuse_emailtext | Where to report abuse coming from this address. |
reverse_dnstext | The address's host name, as DNS gives it, with a dot at the end, such as dns.google.. No reverse DNS found when it has none. |
remarktext | Why the address got its fraud_score, such as IP is likely a VPN or proxy, Missing or invalid abuse contact info. When there's no reason to give, the remarks from the registration record, if any. |
Location#
The location is approximate: often the centre of a city, sometimes only the country. It's never a street address.
| Field | What it means |
|---|---|
citytext | The city, in English. |
countrytext | The country, in English, such as United States. |
country_codetext | The country's two-letter code, such as US. |
continet_codetext | The continent's two-letter code: AF, AN, AS, EU, NA, OC or SA. The misspelt name is the first version's, kept so older code keeps working. |
continet_nametext | The continent, in English, such as North America. Spelling kept, as above. |
in_eutrue/false | The country is in the European Union. |
latitudetext | Degrees north (negative: south), as text with six decimals, such as "37.405990". Turn it into a number before you use it. 0.000000 or empty when unknown. |
longitudetext | Degrees east (negative: west), as text, like latitude. |
timezonetext | The time zone's name, such as America/Los_Angeles. Empty when the city isn't known. |
timezone_offsetnumber | The time zone's difference from UTC at the moment of the lookup, in whole hours, such as -8. It follows daylight saving. Half hours are dropped: India (UTC+5:30) shows 5. 0 when the time zone isn't known. |
localetext | A likely language and country, such as en-US. |
currencytext | The country's currency code, such as USD. |
currencysymboltext | The currency's symbol, such as $. Note: no underscore in this name. |
Risk#
| Field | What it means |
|---|---|
is_tor_exit_pointtrue/false | A Tor exit: traffic from the Tor network comes out here, so the real sender is hidden. |
is_anonimoustrue/false | Looks like a VPN or proxy: the address is in a list of VPN networks, or its registration record mentions words such as VPN, proxy or tunnel. The misspelt name is kept so older code keeps working. |
on_block_listtrue/false | On a public block list of addresses recently reported for attacks or spam. |
on_drop_listtrue/false | In a network on Spamhaus's DROP lists: networks taken over by, or run by, spammers and criminals. Drop all traffic from these. |
recent_spam_domaintrue/false | Seen sending spam in the last 30 days. Despite its name, it's about the address, not a domain. |
fraud_scoretext | The overall risk: low, medium or high. Empty for private addresses. A Tor exit, a VPN or proxy, a block list, or a network known for abuse raises it a lot. A cloud or hosting network, and a registration record without an abuse contact, raise it a little. On a drop list, it's always high. |
Match results to your list#
Python:
import os
import requests
addresses = ["8.8.8.8", "1.1.1.1", "192.168.1.20"]
response = requests.post(
"https://service.cerberius.com/api/ip-lookup",
headers={"X-API-Key": os.environ["CERBERIUS_API_KEY"]},
json={"data": addresses},
timeout=100,
)
answer = response.json()
if response.status_code != 200:
raise RuntimeError(f"{response.status_code}: {answer['error']['message']}")
by_address = {result["ip_address"]: result for result in answer["data"]}
for address in addresses:
result = by_address[address]
print(address, result["lookup_status"], result.get("country"), result.get("fraud_score"))
Node.js (18 or later, in a .mjs file):
const addresses = ["8.8.8.8", "1.1.1.1", "192.168.1.20"];
const response = await fetch("https://service.cerberius.com/api/ip-lookup", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": process.env.CERBERIUS_API_KEY,
},
body: JSON.stringify({ data: addresses }),
signal: AbortSignal.timeout(100_000),
});
const answer = await response.json();
if (!response.ok) throw new Error(`${response.status}: ${answer.error.message}`);
const byAddress = new Map(answer.data.map((result) => [result.ip_address, result]));
for (const address of addresses) {
const result = byAddress.get(address);
console.log(address, result.lookup_status, result.country, result.fraud_score);
}
Credit for the location data#
The location and network data come from DB-IP, under the CC BY 4.0 licence. If you show it to people, such as a country or city on a page, add a credit nearby: "IP data by DB-IP".