Perl client for the ipdata.co IP geolocation and threat intelligence API.
- Full API coverage -- single IP lookup, bulk lookup (up to 100 IPs), field filtering, single-field queries
- Threat intelligence -- TOR, proxy, datacenter, VPN, and botnet detection
- Rich data -- geolocation, ASN, company, carrier, currency, timezone, and language info
- EU endpoint -- route requests through EU-only servers for GDPR compliance
- Zero non-core dependencies -- uses only
HTTP::TinyandJSON::PP(core since Perl 5.14) - Production ready -- input validation, structured error handling, configurable timeouts
From CPAN:
cpanm Net::IPDataOr manually:
perl Makefile.PL
make
make test
make installuse Net::IPData;
my $ipdata = Net::IPData->new(api_key => 'YOUR_API_KEY');
my $result = $ipdata->lookup('8.8.8.8');
printf "%s is in %s, %s\n",
$result->{ip},
$result->{city},
$result->{country_name};Get a free API key (1,500 requests/day) at ipdata.co.
my $result = $ipdata->lookup('8.8.8.8');
printf "IP: %s\n", $result->{ip};
printf "City: %s\n", $result->{city};
printf "Region: %s\n", $result->{region};
printf "Country: %s (%s)\n", $result->{country_name}, $result->{country_code};
printf "Continent: %s\n", $result->{continent_name};
printf "Coords: %.4f, %.4f\n", $result->{latitude}, $result->{longitude};
printf "ASN: %s (%s)\n", $result->{asn}{asn}, $result->{asn}{name};
printf "Timezone: %s\n", $result->{time_zone}{name};
printf "Currency: %s (%s)\n", $result->{currency}{name}, $result->{currency}{code};
printf "EU: %s\n", $result->{is_eu} ? 'Yes' : 'No';
printf "Threat: %s\n", $result->{threat}{is_threat} ? 'Yes' : 'No';my $me = $ipdata->lookup_mine();
printf "My IP: %s\n", $me->{ip};Request only the fields you need to reduce response size and improve performance:
my $partial = $ipdata->lookup('8.8.8.8', fields => [qw(ip country_name asn)]);
printf "Country: %s, ASN: %s\n",
$partial->{country_name},
$partial->{asn}{name};Fields can also be passed as a comma-separated string:
my $partial = $ipdata->lookup('8.8.8.8', fields => 'ip,country_name,asn');Look up to 100 IP addresses in a single request:
my $results = $ipdata->bulk(['8.8.8.8', '1.1.1.1', '9.9.9.9']);
for my $r (@$results) {
printf "%s -> %s, %s\n", $r->{ip}, $r->{city}, $r->{country_name};
}
# With field filtering
my $results = $ipdata->bulk(
['8.8.8.8', '1.1.1.1'],
fields => [qw(ip country_name threat)],
);Retrieve individual fields directly without downloading the full response:
my $asn = $ipdata->asn('8.8.8.8'); # hashref
my $threat = $ipdata->threat('8.8.8.8'); # hashref
my $carrier = $ipdata->carrier('8.8.8.8'); # hashref
my $tz = $ipdata->time_zone('8.8.8.8'); # hashref
my $curr = $ipdata->currency('8.8.8.8'); # hashref
my $langs = $ipdata->languages('8.8.8.8'); # arrayref
my $country = $ipdata->country_name('8.8.8.8'); # string
my $code = $ipdata->country_code('8.8.8.8'); # string
my $eu = $ipdata->is_eu('8.8.8.8'); # boolean
# Or use the generic method for any path-accessible field
my $city = $ipdata->lookup_field('8.8.8.8', 'city');Route all requests through EU-only servers (Paris, Ireland, Frankfurt) for GDPR compliance:
my $ipdata = Net::IPData->new(
api_key => 'YOUR_API_KEY',
eu => 1,
);my $ipdata = Net::IPData->new(
api_key => 'YOUR_API_KEY',
base_url => 'https://custom-proxy.example.com',
);All methods croak on errors. Use eval or Try::Tiny to catch them:
use Try::Tiny;
try {
my $result = $ipdata->lookup('invalid');
} catch {
warn "API error: $_";
};Error messages include the HTTP status code and the API's error message:
| Status | Meaning |
|---|---|
| 401 | Invalid or missing API key |
| 403 | Access forbidden |
| 429 | Rate limit exceeded |
| 599 | Network/connection failure |
| Parameter | Type | Default | Description |
|---|---|---|---|
api_key |
string | required | Your ipdata.co API key |
eu |
bool | 0 |
Use the EU endpoint (eu-api.ipdata.co) |
base_url |
string | https://api.ipdata.co |
Custom base URL (overrides eu) |
timeout |
int | 30 |
HTTP timeout in seconds |
| Method | Returns | Description |
|---|---|---|
lookup($ip, %opts) |
hashref | Full lookup for an IP (omit $ip for caller's IP) |
lookup_mine(%opts) |
hashref | Full lookup for the caller's own IP |
lookup_field($ip, $field) |
varies | Single field via path API |
bulk(\@ips, %opts) |
arrayref | Bulk lookup for up to 100 IPs |
asn($ip) |
hashref | ASN data (asn, name, domain, route, type) |
threat($ip) |
hashref | Threat flags (is_tor, is_proxy, is_threat, ...) |
carrier($ip) |
hashref | Mobile carrier (name, mcc, mnc) |
currency($ip) |
hashref | Currency (name, code, symbol, native, plural) |
time_zone($ip) |
hashref | Timezone (name, abbr, offset, is_dst, current_time) |
languages($ip) |
arrayref | Languages (name, native, code) |
country_name($ip) |
string | Country name |
country_code($ip) |
string | ISO 3166-1 alpha-2 country code |
is_eu($ip) |
boolean | EU member state |
A full lookup returns a hashref with this shape:
{
ip => "8.8.8.8",
is_eu => false,
city => "Mountain View",
region => "California",
region_code => "CA",
country_name => "United States",
country_code => "US",
continent_name => "North America",
continent_code => "NA",
latitude => 37.386,
longitude => -122.0838,
postal => "94035",
calling_code => "1",
flag => "https://ipdata.co/flags/us.png",
emoji_flag => "\x{1f1fa}\x{1f1f8}",
emoji_unicode => "U+1F1FA U+1F1F8",
asn => { asn, name, domain, route, type },
company => { name, domain, network, type },
carrier => { name, mcc, mnc },
languages => [{ name, native, code }],
currency => { name, code, symbol, native, plural },
time_zone => { name, abbr, offset, is_dst, current_time },
threat => { is_tor, is_icloud_relay, is_proxy, is_datacenter,
is_anonymous, is_known_attacker, is_known_abuser,
is_threat, is_bogon },
}
- Perl 5.10.1 or later
- Core modules only: HTTP::Tiny, JSON::PP, Carp, Scalar::Util
- Recommended: IO::Socket::SSL and Net::SSLeay for HTTPS support
Run the unit tests (no API key needed):
make testRun the full suite including live API tests:
IPDATA_API_KEY=your_key_here make testThis module is free software; you can redistribute it and/or modify it under the same terms as Perl itself.