JavaScript library

macadress.com for JavaScript.

A typed JavaScript and TypeScript client for the MAC address and OUI vendor lookup API: vendor name, IEEE block, country, address type, EUI-64 / IPv6 derivation, randomization confidence and a device guess. Zero dependencies, runs anywhere there is fetch.

On npm: macadress. Also on JSR as @macadress/macadress for Deno. MIT licensed, ships ESM, CommonJS and type declarations. Source at github.com/sapisos/macadress-js.

Install

npm install macadress          # Node, Bun, bundlers
deno add jsr:@macadress/macadress   # Deno

Or import straight from a CDN in the browser: import { Client } from "https://esm.sh/macadress".

Look up a vendor

The name-only lookup needs no API key:

import { Client } from "macadress";

const macadress = new Client();

await macadress.vendor("00:03:93:AB:12:34");   // "Apple, Inc."
await macadress.vendor("02:1a:2b:3c:4d:5e");   // null (unregistered, private, or randomized)

Full analysis

Everything else needs a free API key. Typed getters are properties, not calls:

const macadress = new Client("mk_live_xxx");

const r = await macadress.lookup("3C:22:FB:12:34:56");

r.organization;              // string | null
r.country;                   // "US" | null
r.blockType;                 // "MA-L" | "MA-M" | "MA-S" | "IAB" | "CID" | null
r.isPotentiallyRandomized;   // boolean
r.randomizationConfidence;   // "none" | "possible" | "likely"
r.eui64;                     // "3E:22:FB:FF:FE:12:34:56" | null
r.device.category;           // "unknown" (usually)
r.explanation;               // plain-English summary

// anything not covered by a typed getter is still reachable
r.get("vendor_location.city");
r.raw;

Batch and directory search

for (const item of await macadress.batch(["00:03:93:00:00:00", "3C:22:FB:00:00:00", "bad"])) {
  console.log(item.failed ? `${item.input} -> ${item.error}` : `${item.input} -> ${item.organization}`);
}

const hits = await macadress.searchVendors("Cisco", { country: "US", limit: 20 });
for (const block of hits) {
  console.log(`${block.organization} (${block.country})`);
}

Errors

Every failure is an instance of MacadressError: InvalidMacError (400), AuthenticationError (401), RateLimitError (429, with .retryAfter), QuotaExceededError, TransportError for a network failure. Each carries .statusCode, .requestId and .responseBody.

import { Client, RateLimitError } from "macadress";

try {
  const r = await macadress.lookup(input);
} catch (error) {
  if (error instanceof RateLimitError) {
    await new Promise((wake) => setTimeout(wake, (error.retryAfter ?? 5) * 1000));
  } else {
    throw error;
  }
}

Runtimes

The client calls the platform fetch, so it runs unchanged on Node.js 18+, Deno, Bun, Cloudflare Workers and browsers. On an older runtime, pass your own implementation: new Client("mk_live_xxx", { fetch: myFetch }). TypeScript types are bundled; no @types package to install.

Quota

Each lookup(), batch() address, and searchVendors() call is one API call against your plan, the same as a direct REST call: see pricing. The keyless vendor() endpoint is free for 1,000 lookups a day per IP; passing a key lifts that to your plan quota.

Links