Prerequisites
- Node.js 18 or higher (the SDK uses the native
fetch) - An ip-api.io API key — get one free
- TypeScript is optional — types ship with the package
Install the package
Add the official ip-api-io package to your project. It has
zero dependencies, ships both ESM and CommonJS builds, and includes
TypeScript types — nothing else to install.
npm install ip-api-io
# or: pnpm add ip-api-io / yarn add ip-api-io Initialize the client
Import IpApiClient and create one instance with your API key. Read the
key from an environment variable — never hardcode it. The API rejects keyless
requests with 401.
import { IpApiClient } from "ip-api-io";
const client = new IpApiClient({
apiKey: process.env.IP_API_IO_KEY,
// baseUrl and timeoutMs are optional:
timeoutMs: 10000, // per-request timeout (default 10s)
}); Using CommonJS instead of ES modules? The package supports both:
const { IpApiClient } = require("ip-api-io"); Look up an IP address
Call lookup(ip) for any IPv4 or IPv6 address, or lookup()
with no argument to resolve the caller's own IP. Every response includes a
location object and a suspicious_factors object.
const info = await client.lookup("8.8.8.8");
console.log(info.ip); // "8.8.8.8"
console.log(info.isp); // "Google LLC"
console.log(info.location.country); // "United States"
console.log(info.location.city); // "Mountain View"
console.log(info.location.latitude,
info.location.longitude); // 37.4056 -122.0775
console.log(info.location.timezone); // "America/Los_Angeles"
// Resolve the caller's own IP (no argument):
const me = await client.lookup();
console.log(me.ip, me.location.country); lookup() returns a typed
IpInfo object, so info.location.city and
info.suspicious_factors.is_vpn autocomplete and type-check out of the box.
Detect VPN, proxy, Tor & batch lookups
The suspicious_factors object carries seven boolean flags on every
lookup — this is what sets ip-api.io apart from plain geolocation APIs. You get
security intelligence in the same call, at no extra cost.
const info = await client.lookup("185.220.101.45"); // example Tor exit node
const sf = info.suspicious_factors;
if (sf.is_vpn || sf.is_proxy || sf.is_tor_node) {
console.log("Anonymized traffic — consider a CAPTCHA or block");
}
if (sf.is_datacenter) console.log("Cloud/datacenter IP — likely automated");
if (sf.is_threat) console.log("Active threat signal — block this request");
const isRisky = sf.is_vpn || sf.is_proxy || sf.is_tor_node || sf.is_threat;
Need to check many IPs at once? lookupBatch takes up to 100 addresses in
a single request (the SDK validates the limit before any network call):
const batch = await client.lookupBatch(["8.8.8.8", "1.1.1.1", "9.9.9.9"]);
console.log(batch.total_processed, batch.successful_lookups);
for (const [ip, result] of Object.entries(batch.results)) {
console.log(ip, result.location.country, result.suspicious_factors.is_vpn);
} client.riskScore(ip) for a combined
fraud risk score from
0.0 to 1.0.
Production-ready error handling
The client throws typed errors so you can react to each failure precisely. It
never retries on its own — on a rate limit, RateLimitError
tells you exactly when to try again.
import {
IpApiClient,
RateLimitError,
AuthenticationError,
InvalidRequestError,
ServerError,
} from "ip-api-io";
const client = new IpApiClient({ apiKey: process.env.IP_API_IO_KEY });
try {
const info = await client.lookup("8.8.8.8");
// ... use info
} catch (err) {
if (err instanceof RateLimitError) {
// HTTP 429 — quota exhausted
const waitMs = (err.reset ?? 0) * 1000 - Date.now();
console.warn(`Rate limited. Remaining: ${err.remaining}. Retry in ${waitMs}ms`);
} else if (err instanceof AuthenticationError) {
console.error("Invalid or missing API key — get one free at https://ip-api.io");
} else if (err instanceof InvalidRequestError) {
console.error("Bad request:", err.message);
} else if (err instanceof ServerError) {
console.error("ip-api.io is having issues — try again later");
} else {
throw err; // network / timeout error from fetch
}
} RateLimitError.reset (a Unix timestamp) to schedule the next attempt
instead of hammering the API.
In an Express app, run the lookup server-side and pass the real client IP from the
X-Forwarded-For header:
import express from "express";
import { IpApiClient } from "ip-api-io";
const app = express();
app.set("trust proxy", 1);
const client = new IpApiClient({ apiKey: process.env.IP_API_IO_KEY });
app.get("/check", async (req, res) => {
const xff = req.headers["x-forwarded-for"];
const clientIp = (Array.isArray(xff) ? xff[0] : xff?.split(",")[0])?.trim() || req.ip;
try {
const info = await client.lookup(clientIp);
res.json({
ip: info.ip,
country: info.location.country,
is_vpn: info.suspicious_factors.is_vpn,
is_threat: info.suspicious_factors.is_threat,
});
} catch (err) {
// Treat ip-api.io as a non-critical dependency
res.status(200).json({ ip: clientIp, country: null });
}
});