npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

e164-node

v2.0.1

Published

Zero-dependency Node.js SDK for the e164.com phone number lookup API.

Readme

E164 Node SDK

A zero-dependency Node.js SDK for the e164.com phone number lookup API. Give it a phone number, get back the country, number type, and operator behind it.

Installation

npm install e164-node

No runtime dependencies — the SDK uses the built-in fetch, so Node.js 18 or newer is all you need.

Usage

const E164 = require('e164-node');

const e164 = new E164();

async function lookupNumber(phoneNumber) {
  const response = await e164.lookup(phoneNumber);

  if (!response.isSuccess()) {
    console.error('Lookup failed:', response.statusCode, response.error);
    return;
  }

  console.log('Prefix:        ', response.prefix);          // '44113391'
  console.log('Calling code:  ', response.calling_code);    // 44
  console.log('Country (ISO3):', response.iso3);            // 'GBR'
  console.log('Type:          ', response.type);            // 'GEOGRAPHIC'
  console.log('Operator:      ', response.operator_brand);  // 'BT'
}

lookupNumber('+441133910781');

ES modules work too:

import E164 from 'e164-node';

const e164 = new E164();
const response = await e164.lookup('+441133910781');

lookup() never throws for network or API failures. Every outcome — invalid input, an unknown number, a timeout, a dead connection — comes back as a Response, and isSuccess() tells the two apart.

Number formats

Pass numbers however you have them. The SDK reduces input to the digits the API expects, so +, spaces, dashes, dots and parentheses are all fine:

await e164.lookup('+441133910781');
await e164.lookup('441133910781');
await e164.lookup('+44 113 391 0781');
await e164.lookup('+44-113-391-0781');
await e164.lookup(441133910781);        // numbers are accepted too

Input that cannot be an E.164 number is rejected locally with a 400, without a network round trip — no digits at all, more than 15 digits, or a leading 0 (E.164 country codes never start with one, so strip any trunk prefix or international access code: 0044… should be +44…).

Configuration

const e164 = new E164({
  timeout: 5000,                       // per-request timeout in ms (default 10000; 0 disables)
  baseUrl: 'https://e164.com',         // override the API origin
  headers: { 'X-Trace-Id': 'abc123' }, // extra headers on every request
  fetch: myFetch,                      // custom fetch implementation
});

| Option | Type | Default | Description | | --------- | ------------------------- | -------------------- | ----------- | | timeout | number | 10000 | Per-request timeout in milliseconds. 0 disables it. | | baseUrl | string | https://e164.com | API origin. Trailing slashes are trimmed. | | headers | Record<string, string> | — | Extra headers merged into every request. | | fetch | typeof fetch | globalThis.fetch | A fetch implementation to use instead of the global one. | | client | { get(path, config) } | — | An axios-style instance, for v1 compatibility. Takes precedence over fetch. |

Per-request options

const response = await e164.lookup('+441133910781', {
  timeout: 2000,
  signal: AbortSignal.timeout(2000),
});

| Option | Type | Description | | --------- | ------------- | ----------- | | timeout | number | Timeout for this request only. | | signal | AbortSignal | Cancels this request. A cancelled lookup resolves with status 499 — except for AbortSignal.timeout(), whose deadline is reported as 504 like any other timeout. |

Response object

| Property | Type | Description | | ------------- | ------------------- | ----------- | | statusCode | number | HTTP status, or an SDK-assigned status for local failures. | | error | string \| null | Error message, or null on success. | | data | object \| null | The best-matching record, or null on failure. | | results | object[] | Every record returned, most specific first. Empty on failure. | | rawResponse | object \| null | The underlying HTTP response, for headers, status and other low-level details. | | isSuccess() | () => boolean | true when statusCode is 2xx. | | toJSON() | () => object | A JSON-safe view, omitting rawResponse. |

rawResponse has an already-consumed body. lookup() reads the body itself in order to parse it, so on the default fetch transport rawResponse.text() and rawResponse.json() throw Body is unusable. Read rawResponse.headers and rawResponse.status freely; use response.data or response.results for the body.

On success, the fields of the best-matching record are also copied onto the response itself:

| Field | Type | Example | | ------------------ | ----------------- | ------- | | prefix | string \| null | '44113391' | | calling_code | number \| null | 44 | | iso3 | string \| null | 'GBR' | | tadig | string \| null | 'DEUD1' | | mccmnc | string \| null | '23450' | | type | string \| null | 'GEOGRAPHIC', 'MOBILE' | | location | string \| null | 'France' | | operator_brand | string \| null | 'BT' | | operator_company | string \| null | 'BT' | | total_length_min | number \| null | 12 | | total_length_max | number \| null | 12 | | weight | number \| null | 11 | | source | string \| null | 'e164.com' |

calling_code, total_length_min, total_length_max and weight are JSON numbers. (The v1 type declarations wrongly described them as strings.)

Status codes

| Status | Meaning | | ------ | ------- | | 200 | Match found. | | 400 | Input rejected locally; no request was made. | | 404 | The API has no record for this number. | | 429 | Rate limited by the API. See Rate limits. | | 499 | The request was cancelled via an AbortSignal. | | 500 | Network or unexpected error. | | 502 | The API replied with something unusable — a redirect, or a body that is not JSON. | | 504 | The request timed out. |

Any other status is passed through from the API as-is.

Rate limits

The API is rate-limited by a token bucket, and parallelism is what empties it. A burst of concurrent lookups will see most of them rejected with 429:

// Don't do this. Roughly two thirds of these come back 429.
const results = await Promise.all(numbers.map((n) => e164.lookup(n)));

Two things make this harder to handle than a typical rate limit:

  • The 429 carries no Retry-After header, so there is nothing to schedule against.
  • Once the bucket is empty it stays empty for tens of seconds. Millisecond-scale backoff does not clear it — in testing, even one-at-a-time lookups spaced 250ms apart kept failing until the client had been idle for roughly a minute.

The SDK does not retry, so batching is the caller's responsibility. Go sequential, and back off in seconds rather than milliseconds:

async function lookupAll(e164, numbers) {
  const results = [];

  for (const number of numbers) {
    results.push(await lookupWithRetry(e164, number));
  }

  return results;
}

async function lookupWithRetry(e164, number, attempts = 5) {
  for (let attempt = 0; ; attempt++) {
    const response = await e164.lookup(number);

    if (response.statusCode !== 429 || attempt >= attempts - 1) return response;

    // Seconds, not milliseconds: 1s, 2s, 4s, 8s.
    await new Promise((resolve) => setTimeout(resolve, 1000 * 2 ** attempt));
  }
}

That resolves 30 numbers without a single failure, in about 40 seconds. Adding concurrency to it makes it slower, not faster, because every parallel request spends the budget the retries are waiting to recover.

From a rested start, sequential lookups at up to ~5/second were not limited at all — so a steady trickle needs no retry logic. It is bursts, and anything recovering from one, that need the backoff above.

TypeScript

Type declarations ship with the package; no @types install is needed.

import E164 = require('e164-node');
import Response = require('e164-node/lib/response');

const e164 = new E164({ timeout: 5000 });
const response: Response = await e164.lookup('+441133910781');

if (response.isSuccess()) {
  const record: Response.E164LookupData | null = response.data;
  const callingCode: number | null = response.calling_code;
}

Development

npm install
npm test          # unit tests — fully offline
npm run test:live # integration tests against the real API
npm run typecheck # verify the published .d.ts files compile
npm run check     # typecheck + unit tests

npm run test:live is worth running before every publish. The v1 +-prefix bug passed a fully mocked suite for its whole lifetime; only a real request exposed it.

Migrating from v1

v1 required axios as a separate install and was broken for any number written in E.164 form. See CHANGELOG.md for the full list of changes.

For most users the upgrade is:

-npm install e164-node axios
+npm install e164-node

with no code changes. If you passed a custom axios instance via options.client, that still works.

Contributing

Contributions are welcome — please open an issue or pull request on GitHub.

License

ISC