InternetDataInternetData

Node.js

The official Node.js client library for the InternetData API.

See on GitHub

Getting Started

npm install @internetdata/internetdata

Requires Node.js 22 or newer. TypeScript types are included.

Usage

Every database call needs an API key carrying the db.download scope. Databases are licensed by contract rather than bought self-serve, so a key arrives with the license; see the API documentation or write to dev@internetdata.io.

import { InternetData } from '@internetdata/internetdata';

const client = new InternetData({ apiKey: process.env.INTERNETDATA_API_KEY });

for (const db of await client.database.list()) {
    console.log(db.base, db.standing, db.versions.map((v) => v.id).join(', '));
}

list() returns one entry per database FAMILY, because a license is held against the family while a download names a specific version. standing is licensed if the family is yours today, expired if the term has ended, and unlicensed if it is published but has never been bought. versions carries the ids you pass everywhere else, oldest first, and the formats each one is actually built in.

What is inside a database

const meta = await client.database.metadata('vpn_ip_v1');

console.log(meta.updated);        // '2026-09-04', the day this build was generated
console.log(meta.entries);        // rows in the build
console.log(meta.size.csvgz);     // bytes you are about to move
console.log(meta.schema.csvgz);   // [{ name, type, description }, ...]
console.log(meta.sample?.mmdb);   // a few real rows

Poll updated and entries to decide whether today's build is worth fetching; both are free of the transfer. size is also the honest way to budget a download before starting one.

Downloading

download streams a file to a path and returns the number of bytes written. Nothing larger than a chunk is ever held in memory, whatever the database weighs:

const written = await client.database.download('vpn_ip_v1', 'mmdb', './vpn_ip_v1.mmdb');
console.log(`${written} bytes`);

The bytes go to a neighboring .part file and the name only appears once the transfer completes, so an interruption cannot leave a truncated file that reads as a whole database. You can pass a writable stream instead of a path, in which case it stays yours to close and gets no such treatment.

Or take the link and run the transfer yourself. The API answers a redirect to time-limited object storage, and that URL authorizes itself, so it can be handed to something holding no API key:

const url = await client.database.downloadUrl('vpn_ip_v1', 'mmdb');

Or, for a small database, take the bytes directly:

const bytes = await client.database.downloadBytes('bogon_ip_v1', 'csvgz');

The formats are csvgz and mmdb. They're exported as DATABASE_FORMATS, beside STANDINGS and LICENSE_TYPES, for code that takes one from a command line or a config file. Anything else is refused before a request goes out, as a bad_request naming the formats that exist:

import { DATABASE_FORMATS } from '@internetdata/internetdata';

if (!DATABASE_FORMATS.includes(format)) {
    console.error(`--format must be one of ${DATABASE_FORMATS.join(', ')}`);
}

downloadBytes holds the whole file in memory and the catalog spans seven orders of magnitude, from a few hundred bytes to over 5 GiB, so use download for anything you have not checked with metadata first.

Verifying what you got

const sums = await client.database.checksums('vpn_ip_v1', 'mmdb');
console.log(sums.sha256);   // also md5, sha1, sha512

Download history

Your organization's recent attempts, newest first, refusals included, because a denial is what answers "it stopped working" and its absence answers nothing:

for (const d of await client.database.downloads({ limit: 20 })) {
    console.log(d.created, d.dataset_id, d.format, d.outcome, d.http_status);
}

Errors

Failures throw an InternetDataError carrying a kind and a retryable flag:

import { InternetDataError } from '@internetdata/internetdata';

try {
    await client.database.download('vpn_ip_v1', 'mmdb', './vpn_ip_v1.mmdb');
} catch (err) {
    if (err instanceof InternetDataError) {
        console.error(err.kind, err.status, err.retryable);
    }
}

kind is one of bad_request, unauthorized, forbidden, rate_limited, quota_exceeded, server_error or network. The message is the API's own result code, such as NOT_LICENSED or LICENSE_EXPIRED, which is usually the specific thing you want to read.

Note that rate_limited and quota_exceeded both arrive as HTTP 429 and are not the same thing. A rate limit is the API facing a burst, so retrying later works; a spent quota needs your allowance raised or the window to roll over. The library retries the first for you and never the second.

A failure part way through a transfer is rethrown as it arrived rather than wrapped: a reset socket and a full disk are different problems, and only one of them is ours.

Sign in with OAuth (device flow)

A program running on the person's own machine can let them sign in with a browser and pick one of their API keys, instead of asking them to paste it:

const client = new InternetData();

const device = await client.oauth.deviceAuthorization('your-client-id', {
    scope: 'account.read apikeys.read apikeys.reveal',
});
console.log(`Open ${device.verification_uri} and enter ${device.user_code}`);

const token = await client.oauth.pollDeviceToken('your-client-id', device);
if (token.apikey === undefined) {
    throw new Error("no API key came back: none was picked, or it can't be shown again");
}
const keyed = new InternetData({ apiKey: token.apikey });

A denied sign-in rejects with OauthAccessDeniedError and a code that ran out with OauthExpiredTokenError. Client IDs are issued on request from support@internetdata.io, and client.oauth.revoke('your-client-id', token.refresh_token) signs the machine out again.

On this page