Official, dependency-free Node.js / TypeScript client for the ipwhois.io IP Geolocation API.
- ✅ Single and bulk IP lookups (IPv4 and IPv6)
- ✅ Works with both the Free and Paid plans
- ✅ HTTPS by default
- ✅ Localisation, field selection, threat detection, rate info
- ✅ Never throws — all errors returned as
{ success: false }objects - ✅ No runtime dependencies — only Node's built-in
node:http/node:https - ✅ First-class TypeScript types
- ✅ Node.js 18+
npm install ipwhois-nodeThe same IPWhois class is used for both plans. The only difference is whether
you pass an API key:
- Free plan — create the client without arguments. No API key, no signup required. Suitable for low-traffic and non-commercial use.
- Paid plan — create the client with your API key from https://ipwhois.io. Higher limits, plus access to bulk lookups and threat-detection data.
import { IPWhois } from 'ipwhois-node';
const free = new IPWhois(); // Free plan — no API key
const paid = new IPWhois('YOUR_API_KEY'); // Paid plan — with API keyEverything else (lookup(), options, error handling) is identical.
import { IPWhois } from 'ipwhois-node';
const ipwhois = new IPWhois(); // no API key
const info = await ipwhois.lookup('8.8.8.8');
if (info.success) {
console.log(`${info.country} ${info.flag?.emoji}`);
// → United States 🇺🇸
console.log(`${info.city}, ${info.region}`);
// → Mountain View, California
}Get an API key at https://ipwhois.io and pass it to the constructor:
import { IPWhois } from 'ipwhois-node';
const ipwhois = new IPWhois('YOUR_API_KEY'); // with API key
const info = await ipwhois.lookup('8.8.8.8');
if (info.success) {
console.log(`${info.country} ${info.flag?.emoji}`);
// → United States 🇺🇸
console.log(`${info.city}, ${info.region}`);
// → Mountain View, California
}ℹ️ Pass nothing to look up your own public IP:
await ipwhois.lookup();— works on both plans.
Every option below can be passed per call, or set once on the client as a default.
| Option | Type | Plans needed | Description |
|---|---|---|---|
lang |
string |
Free + Paid | One of: en, ru, de, es, pt-BR, fr, zh-CN, ja |
fields |
string[] |
Free + Paid | Restrict the response to specific fields (e.g. ['country', 'city']) |
rate |
boolean |
Basic and above | Include the rate block (limit, remaining) |
security |
boolean |
Business and above | Include the security block (proxy/vpn/tor/hosting) |
Every option can be passed two ways: per call (as the second argument to
lookup() / bulkLookup()) or once as a default on the client. Per-call
options always override the defaults, so it's safe to set sensible defaults
and only override what differs for a specific call.
Defaults are set with fluent setters — setLanguage(), setFields(),
setSecurity(), setRate(), setTimeout(), setConnectTimeout(),
setUserAgent() — and can be chained:
import { IPWhois } from 'ipwhois-node';
// Free plan
const ipwhois = new IPWhois()
.setLanguage('en')
.setFields(['success', 'country', 'city', 'flag.emoji'])
.setTimeout(8_000);import { IPWhois } from 'ipwhois-node';
// Paid plan
const ipwhois = new IPWhois('YOUR_API_KEY')
.setLanguage('en')
.setFields(['success', 'country', 'city', 'flag.emoji'])
.setTimeout(8_000);Either client behaves the same way at call time — per-call options always win over the defaults:
await ipwhois.lookup('8.8.8.8'); // uses lang=en, the field whitelist, and timeout=8000
await ipwhois.lookup('1.1.1.1', { lang: 'de' }); // overrides lang for this single call only
⚠️ When you restrict fields withsetFields()(or the per-callfieldsoption), the API only returns the fields you ask for. Always include'success'in the list if you rely onresult.successfor error checking — otherwise the field will be missing on responses.
ℹ️
setSecurity(true)requires Business+ andsetRate(true)requires Basic+. See the table above for what's available where.
By default, all requests are sent over HTTPS. If you need to disable it (for
example, in environments without an up-to-date CA bundle), pass ssl: false
to the constructor:
import { IPWhois } from 'ipwhois-node';
// Free plan
const ipwhois = new IPWhois(null, { ssl: false });import { IPWhois } from 'ipwhois-node';
// Paid plan
const ipwhois = new IPWhois('YOUR_API_KEY', { ssl: false });ℹ️ HTTPS is strongly recommended for production traffic — your API key is sent in the query string and would otherwise travel in clear text.
The bulk endpoint sends up to 100 IPs in a single GET request. Each address counts as one credit. Available on the Business and Unlimited plans.
import { IPWhois } from 'ipwhois-node';
const ipwhois = new IPWhois('YOUR_API_KEY');
const results = await ipwhois.bulkLookup([
'8.8.8.8',
'1.1.1.1',
'208.67.222.222',
'2c0f:fb50:4003::', // IPv6 is fine — mix freely
]);
// Whole-batch failure (bad key, network down, …) is a single error object.
if (!Array.isArray(results)) {
console.error(`Bulk failed: ${results.message}`);
return;
}
for (const row of results) {
if (!row.success) {
// Per-IP errors (e.g. "Invalid IP address") are returned inline,
// they do NOT throw — the rest of the batch is still usable.
console.log(`skip ${row.ip}: ${row.message}`);
continue;
}
console.log(`${row.ip} → ${row.country}`);
}ℹ️ Bulk requires an API key. Calling
bulkLookup()without one will fail at the API level.
The library never throws. Every failure — invalid IP, bad API key, rate
limit, network outage, bad options — comes back inside the response object
with success: false and a message. Just check result.success after
every call:
const info = await ipwhois.lookup('8.8.8.8');
if (!info.success) {
console.error(`Lookup failed: ${info.message}`);
return;
}
console.log(info.country);This means an outage of the ipwhois.io API (or of your server's DNS,
connection, etc.) will never surface as a fatal error in your application —
you decide how to react. TypeScript also narrows the type for you: inside
the if (info.success) branch, info is IpwhoisSuccess; in the else
branch it's IpwhoisError.
Every error response contains success: false, a human-readable message,
and an error_type so you can branch on the category of the failure. Some
errors include extra fields you can branch on:
| Field | When it's present |
|---|---|
success |
Always — false for error responses (true for successful responses) |
message |
Always — human-readable description of what went wrong |
error_type |
Always — one of 'api', 'network', or 'invalid_argument' |
http_status |
On HTTP 4xx / 5xx responses |
retry_after |
On HTTP 429 — free plan only (the paid endpoint does not send a Retry-After header) |
const info = await ipwhois.lookup('8.8.8.8');
if (!info.success) {
if (info.http_status === 429) {
await new Promise(r => setTimeout(r, (info.retry_after ?? 60) * 1000));
// …retry
}
if (info.error_type === 'network') {
// DNS failure, connection refused, timeout, …
}
console.error(`Error: ${info.message}`);
return;
}A successful response includes (depending on your plan and selected options):
For the full field reference, see the official documentation.
An error response looks like:
{
"success": false,
"message": "Rate limit exceeded",
"error_type": "api", // 'api' / 'network' / 'invalid_argument'
"http_status": 429, // present for HTTP 4xx / 5xx
"retry_after": 60 // additionally present on HTTP 429 — free plan only
}The package ships with full type declarations. The result of lookup() is a
discriminated union:
import type { IpwhoisResult, IpwhoisSuccess, IpwhoisError } from 'ipwhois-node';
const info: IpwhoisResult = await ipwhois.lookup('8.8.8.8');
if (info.success) {
// info is IpwhoisSuccess here
info.country;
} else {
// info is IpwhoisError here
info.message;
}For bulkLookup(), narrow on Array.isArray():
const results = await ipwhois.bulkLookup(['8.8.8.8', '1.1.1.1']);
if (Array.isArray(results)) {
// per-IP results
for (const row of results) { /* … */ }
} else {
// whole-batch failure: results is IpwhoisError
}- Node.js 18 or newer
- No runtime dependencies
Issues and pull requests are welcome on GitHub.
MIT © ipwhois.io
{ "ip": "8.8.4.4", "success": true, "type": "IPv4", "continent": "North America", "continent_code": "NA", "country": "United States", "country_code": "US", "region": "California", "region_code": "CA", "city": "Mountain View", "latitude": 37.3860517, "longitude": -122.0838511, "is_eu": false, "postal": "94039", "calling_code": "1", "capital": "Washington D.C.", "borders": "CA,MX", "flag": { "img": "https://cdn.ipwhois.io/flags/us.svg", "emoji": "🇺🇸", "emoji_unicode": "U+1F1FA U+1F1F8" }, "connection": { "asn": 15169, "org": "Google LLC", "isp": "Google LLC", "domain": "google.com" }, "timezone": { "id": "America/Los_Angeles", "abbr": "PDT", "is_dst": true, "offset": -25200, "utc": "-07:00", "current_time": "2026-05-08T14:31:48-07:00" }, "currency": { "name": "US Dollar", "code": "USD", "symbol": "$", "plural": "US dollars", "exchange_rate": 1 }, "security": { "anonymous": false, "proxy": false, "vpn": false, "tor": false, "hosting": false }, "rate": { "limit": 250000, "remaining": 50155 } }