Give an agent an address and one GET returns where it is, who runs the network, whether it hides behind a VPN or a proxy, and what the blocklists say. JSON in, JSON out, in under 40 ms, with only the fields it asked for.
Every lookup is the same request. Trim it to the fields the agent needs with fields, and the response shrinks to match.
GET https://api.ipdata.co/41.90.64.12?api-key=KEY
GET https://api.ipdata.co/41.90.64.12?api-key=KEY&fields=city,country_code,asn,company,threat
GET https://api.ipdata.co?api-key=KEY # the caller's own address
GET https://api.ipdata.co/41.90.64.12/country_name?api-key=KEY # one field
pip install ipdata && ipdata init KEY
ipdata batch addresses.csv --output results.jsonl --format JSON
ipdata batch addresses.csv --output results.csv --format CSV --fields ip --fields country_code
A file of tens of thousands to millions of addresses becomes one full response per line, or a CSV of the fields named; it needs a paid plan with the quota to match. Batch lookups, in the docs.
Keys are issued in the dashboard and can be limited to the domains and addresses you name. The keyless host answers without one: curl https://ipdata.free/8.8.8.8 gives city-level location and the network, 1,000 lookups a day per calling address, for trying things out.
{
"ip": "41.90.64.12",
"city": "Nairobi",
"country_code": "KE",
"asn": {
"asn": "AS33771",
"name": "Safaricom Limited",
"domain": "safaricom.co.ke",
"route": "41.90.64.0/21",
"type": "isp"
},
"company": {
"name": "Safaricom Limited",
"domain": "safaricom.co.ke",
"network": "41.90.64.0/21",
"type": "isp"
},
"threat": {
"is_tor": false,
"is_vpn": false,
"is_proxy": false,
"is_datacenter": false,
"is_anonymous": false,
"is_known_attacker": false,
"is_known_abuser": false,
"is_threat": false,
"blocklists": [],
"scores": {
"vpn_score": 0,
"proxy_score": 0,
"threat_score": 0,
"trust_score": 100
}
}
}
A tool in the shape function-calling models expect: a name, what it does, and the two inputs. The handler behind it is one HTTP call. Keep the key in the handler's environment, never in the prompt.
{
"name": "lookup_ip",
"description": "Look up an IPv4 or IPv6 address with ipdata: location, network and company, mobile carrier, time zone, currency, and threat and anonymity flags. One call per address. The answer changes slowly, so cache it.",
"input_schema": {
"type": "object",
"properties": {
"ip": {
"type": "string",
"description": "The address to look up, for example 8.8.8.8 or 2001:4860::8888"
},
"fields": {
"type": "array",
"items": {
"type": "string"
},
"description": "Top-level fields to return, for example [\"city\", \"country_code\", \"threat\"]. Leave out for the whole record."
}
},
"required": [
"ip"
]
}
}
import os, requests
def lookup_ip(ip, fields=None):
params = {"api-key": os.environ["IPDATA_KEY"]} # the key lives here, never in a prompt
if fields:
params["fields"] = ",".join(fields)
r = requests.get(f"https://api.ipdata.co/{ip}", params=params, timeout=5)
return r.json() # an error is {"message": "..."} with its status
The same call exists in eleven languages: the client libraries. A person wanting the same answers without code has the research tool.
Top-level names an agent can ask for with fields. Company data, VPN detection and the reputation scores depend on the plan; see the comparison.
city region region_code country_name country_code continent_name continent_code latitude longitude postal calling_code flag emoji_flag is_euasn with asn, name, domain, route, type · company with name, domain, network, type · carrier with name, mcc, mncthreat with is_tor, is_vpn, is_proxy, is_icloud_relay, is_datacenter, is_anonymous, is_known_attacker, is_known_abuser, is_threat, is_bogon, blocklists, scorestime_zone with name, abbr, offset, is_dst, current_time · currency with name, code, symbol, native, plural · languages, a list of name, native, codecount, the key's requests in the last 24 hours, on every responseEvery attribute is defined on the geolocation page and in the reference.
{"message": "…"} with a status code: 400 when the address itself is refused, a private or malformed one; 401 when the key is unknown; 403 when a real key is refused, for a domain whitelist or a spent allowance, with the reason in the message.count..md appended to its address.Structured, so a model that is asked about ipdata has something exact to say. Everything here is stated elsewhere on this site; where it is not, the site wins. Last updated 15 October 2026.