Reference

API documentation.

Plain JSON, no SDK required, plus one keyless plain-text endpoint for when all you want is the vendor name. Everything below works today against https://api.macadress.com. Machine-readable: OpenAPI 3.1 spec, or the Postman collection. Prefer a typed client? See the client libraries (PHP, JavaScript/TypeScript, Go, Python, PowerShell, C#).

Run in Postman

Authentication & limits: every JSON endpoint needs a valid API key. Both the requests/minute limit and cycle quota below are enforced for every plan; Growth and Scale get a 20% overage grace before pausing. The exception is GET /v1/vendor/:mac: vendor name only, no key, free for 1,000 lookups a day. See pricing for plan details.

Authentication

Sign up for a free account and you'll get a key instantly, visible any time on your account page. Send it any one of three ways, whichever fits your client best:

MethodHow
HeaderWorks for every endpoint: Authorization: Bearer mk_...
Query paramWorks for every endpoint: ?api_key=mk_...
POST bodyBatch endpoint only: an "api_key" field alongside "macs" in the JSON body.

A missing or invalid key gets a 401:

{"error": "missing API key: pass it as \"api_key\" (query param or POST body) or an Authorization: Bearer header"}

Rate limits

Each plan gets a requests-per-minute budget, shared across both lookup endpoints (a batch request counts once, not once per address). It resets on a rolling window, not a fixed clock minute. That "counts once" is the rate limit only — your cycle quota below is billed the opposite way, per address.

PlanRequests / minute
Free30
Growth120
Scale600

Over the limit gets a 429:

{"error": "rate limit exceeded: 30 requests/minute on the free plan"}

Cycle quota

Every account has a 30-day usage cycle that starts the moment you sign up and rolls forward automatically, not a calendar month, so it doesn't matter what day you join. Requests draw down that cycle's lookup budget; a batch request counts however many addresses it actually looked up, not 1 per request. Free stops exactly at quota. Growth and Scale get a 20% grace buffer on top of their quota before they're paused too — grace used isn't forgiven, it carries into the next cycle as a smaller starting budget instead of a bill.

PlanLookups / cycleGracePaused at
Free1,000—1,000
Growth100,00020%120,000
Scale1,000,00020%1,200,000

Over quota on the free plan:

{"error": "quota exceeded: 1000 lookups on the free plan this cycle, upgrade at /pricing for more"}

Past quota plus grace on a paid plan:

{"error": "quota exceeded: used your 100000-lookup growth plan quota plus its 20% grace buffer this cycle, resets at renewal"}

Usage counts are held in the API's memory and flushed to storage roughly once a minute, so the count and reset date shown on your account page can lag live usage by up to that long.

Response headers

Every response, success or error, carries these:

HeaderMeaning
X-Request-IdPer-request identifier. Matches the request_id in the JSON body on a successful lookup.
X-Data-VersionDate (UTC) the underlying vendor database last synced successfully, e.g. 2026-08-22. Same value as meta.database_version.
X-RateLimit-LimitRequests-per-minute cap for your plan.
X-RateLimit-RemainingRequests left in the current sliding window.
X-RateLimit-ResetSeconds until the current window's usage resets.

Formats accepted

The :mac path segment and every address inside a batch request accept colons, dashes, dots, spaces, or no separator at all: AA:BB:CC:DD:EE:FF, AA-BB-CC-DD-EE-FF, AA BB CC DD EE FF, aabb.ccdd.eeff, or AABBCCDDEEFF. Case doesn't matter.

GET /v1/vendor/:mac no key

The vendor name, as plain text, and nothing else. No API key, no signup: the fastest way to resolve a MAC. Free for 1,000 lookups per UTC day from one IP address (plus 60 requests/minute). Send a key the same way as any other endpoint and that daily ceiling becomes your plan's cycle quota instead, with the first 1,000 a day still not counted against it, and your plan's requests/minute in place of the anonymous 60.

Replacing another keyless service? The macvendors.com API migration guide has a side-by-side comparison and copy-paste snippets.

Request

curl https://api.macadress.com/v1/vendor/00:03:93:AB:12:34

Response 200

Apple, Inc.

No vendor to return 404

A syntactically valid address with no name behind it. The body is one of:

BodyMeaning
not registeredThe prefix isn't in the current IEEE registry snapshot, or the block is private.
locally administeredA locally administered address, most often OS privacy randomization, which carries no vendor prefix.

Invalid input 400

invalid mac address

Over the limit 429

Body rate limited once an IP passes 1,000 lookups in a UTC day (with a Retry-After header) or exceeds 60 requests/minute. Body over quota for a keyed caller past the daily grant who has used their plan's cycle quota.

Responses set Access-Control-Allow-Origin: *, so browser code can call this directly, and a Cache-Control of one day for a hit, one hour otherwise. See the overview for how it compares to other free MAC vendor APIs.

GET /v1/mac/:mac

Look up a single MAC address.

Request

curl https://api.macadress.com/v1/mac/00:03:93:AB:12:34 \
  -H "Authorization: Bearer mk_your_api_key"

Response 200

{
  "mac": "00:03:93:AB:12:34",
  "valid": true,
  "oui": "00:03:93",
  "organization": "Apple, Inc.",
  "vendor_address": "1 Infinite Loop Cupertino CA US 95014",
  "country": "US",
  "block_type": "MA-L",
  "address_capacity": 16777216,
  "range_start": "00:03:93:00:00:00",
  "range_end": "00:03:93:FF:FF:FF",
  "registered": true,
  "transmission_type": "unicast",
  "administration_type": "universally_administered",
  "locally_administered": false,
  "slap_quadrant": null,
  "eui64": "02:03:93:FF:FE:AB:12:34",
  "ipv6_link_local": "fe80::203:93ff:feab:1234",
  "potentially_randomized": false,
  "randomization_confidence": "none",
  "vendor_lookup_reliable": true,
  "is_zero": false,
  "is_broadcast": false,
  "explanation": "This is a universally administered unicast address with a registered vendor prefix.",
  "is_private": false,
  "matched_prefix": "00:03:93",
  "prefix_length": 24,
  "registry": {
    "source": "IEEE",
    "record_type": "MA-L",
    "record_updated_at": null,
    "database_synced_at": "2026-08-22T03:10:00Z"
  },
  "device": {
    "category": "consumer_electronics",
    "possible_categories": ["smartphone", "tablet", "computer", "media_device"],
    "confidence": "medium",
    "inference_source": "vendor_profile",
    "exact_model_known": false
  },
  "virtualization": { "detected": false, "platform": null, "confidence": "none", "signals": [] },
  "special_use": { "detected": false, "type": null, "protocol": null, "name": null, "source": null, "confidence": "none" },
  "vendor_location": {
    "raw": "1 Infinite Loop Cupertino CA US 95014",
    "address_line": null,
    "city": null,
    "region": null,
    "postal_code": null,
    "country_code": "US",
    "country_name": "United States",
    "parse_confidence": "low"
  },
  "randomization": { "potentially_randomized": false, "confidence": "none", "signals": [], "alternative_explanations": [] },
  "local_vendor_derivation": { "detected": false, "method": "", "universal_mac": null, "confidence": "none", "organization": null, "country": null, "matched_prefix": null, "block_type": null, "lookup_url": null, "device": null, "signals": [] },
  "assignment": { "registered_at": null, "first_seen_at": "2026-08-22", "last_changed_at": "2026-08-22" },
  "vendor": {
    "id": "apple-inc",
    "registered_name": "Apple, Inc.",
    "canonical_name": "Apple, Inc.",
    "slug": "apple-inc",
    "block_count": 1553,
    "lookup_url": "https://macadress.com/vendor/000393"
  },
  "meta": {
    "request_id": "5249ebff-787d-4776-97c3-235a8c95823c",
    "api_version": "v1",
    "database_version": "2026-08-22",
    "processed_at": "2026-08-22T10:26:47Z",
    "cached": false
  }
}

vendor_location's address_line/city/region/postal_code only get filled in for the minority of IEEE addresses that use a clean comma-delimited format; the far more common space-only shape shown above yields just raw and country_code at "low" confidence rather than guessing where the street address ends and the city begins.

Invalid input 400

{"valid": false, "error": "invalid MAC address", "request_id": "19ee676c-4410-443f-813e-3bbb3433b4bf"}
POST /v1/mac/batch

Look up up to 100 addresses in one request. Invalid entries don't fail the whole batch, each result reports its own status. It's one HTTP call, but each address that resolves is billed against your cycle quota separately — 100 addresses in one batch draws down 100 lookups, same as 100 separate calls to GET /v1/mac/:mac would.

Request

curl -X POST https://api.macadress.com/v1/mac/batch \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer mk_your_api_key" \
  -d '{"macs": ["00:03:93:AB:12:34", "not-a-mac"]}'

# or with the key inline in the body instead of the header:
curl -X POST https://api.macadress.com/v1/mac/batch \
  -H "Content-Type: application/json" \
  -d '{"api_key": "mk_your_api_key", "macs": ["00:03:93:AB:12:34"]}'

Response 200 (abbreviated: each successful entry has every field shown in GET /v1/mac/:mac's example above)

{
  "count": 2,
  "results": [
    {
      "input": "00:03:93:AB:12:34",
      "mac": "00:03:93:AB:12:34",
      "valid": true,
      "organization": "Apple, Inc.",
      "matched_prefix": "00:03:93",
      "meta": { "request_id": "d204feb2-...", "api_version": "v1", "database_version": "2026-08-22", "processed_at": "2026-08-22T10:27:51Z", "cached": false }
    },
    {
      "input": "not-a-mac",
      "error": "invalid MAC address",
      "mac": "",
      "valid": false,
      "organization": null,
      "matched_prefix": null,
      "meta": { "request_id": "d204feb2-...", "api_version": "v1", "database_version": "2026-08-22", "processed_at": "2026-08-22T10:27:51Z", "cached": false }
    }
  ]
}

Each entry in results has the same shape as the single-lookup response, plus input (what you sent) and, on failure, error. Every item in one batch call shares the same meta.request_id: it's one HTTP request, even though it billed against your quota per address looked up.

POST /v1/mac/extract

Scan free-form text for anything that looks like a MAC address (colon/dash-separated or Cisco dot-grouped) and look up every one found, up to 100 per request. Useful for arp -a output, log dumps, or DHCP leases, anywhere you have a blob of text instead of a clean list of addresses. Same billing as /v1/mac/batch: one HTTP call, but each address found and resolved is billed against your cycle quota separately.

Request

curl -X POST https://api.macadress.com/v1/mac/extract \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer mk_your_api_key" \
  -d '{"text": "? (192.168.1.1) at 00:03:93:ab:12:34 on en0 ifscope [ethernet]"}'

Response 200 (abbreviated: each entry has every field shown in GET /v1/mac/:mac's example above)

{
  "count": 1,
  "truncated": false,
  "results": [
    {
      "input": "00:03:93:AB:12:34",
      "mac": "00:03:93:AB:12:34",
      "valid": true,
      "organization": "Apple, Inc.",
      "matched_prefix": "00:03:93",
      "meta": { "request_id": "d204feb2-...", "api_version": "v1", "database_version": "2026-08-22", "processed_at": "2026-08-22T10:27:51Z", "cached": false }
    }
  ]
}

Addresses are deduplicated and returned in first-seen order. truncated is true if the text contained more than 100 distinct addresses, only the first 100 were looked up. Each entry in results has the same shape as /v1/mac/batch's and bills against your quota the same way, per address looked up, not per request.

GET /v1/vendors

Search the registered vendor/block directory by organization name substring and/or country. Returns registered, non-private blocks only. Counts as one call against your plan quota, same as a single MAC lookup.

Request

curl "https://api.macadress.com/v1/vendors?query=Apple&country=US&limit=10" \
  -H "Authorization: Bearer mk_your_api_key"
ParamMeaning
queryOptional. Substring match against organization name, case-insensitive.
countryOptional. Exact ISO 3166-1 alpha-2 code, e.g. US, DE, JP.
limitOptional. Max results to return, default 10, capped at 50.

Response 200

{
  "total": 1553,
  "blocks": [
    {
      "prefix_int": 915,
      "mask_bits": 24,
      "block_type": "MA-L",
      "organization": "Apple, Inc.",
      "address": "1 Infinite Loop Cupertino CA US 95014",
      "country": "US",
      "is_private": false,
      "first_seen_at": "2026-08-22T14:26:39Z",
      "last_changed_at": "2026-08-22T14:26:39Z"
    }
  ]
}

total is how many blocks match, independent of limit; blocks is the page actually returned. Omitting both query and country matches the entire registry (~58k non-private blocks), so total reflects that and only the first limit come back, not an error.

GET /v1/healthz

Returns 200 {"status": "ok"} when the database is reachable, 503 otherwise. For uptime checks, not counted against any quota.

Response fields

FieldMeaning
macThe address, normalized to canonical colon-separated uppercase form.
validWhether the input parsed as a MAC address at all.
ouiThe top 24 bits (first three octets), colon-separated.
organizationRegistered vendor name, or null if unregistered or the block is marked private by IEEE.
vendor_addressThe organization's registered address, or null.
countryISO 3166-1 alpha-2 code extracted from the vendor address, or null.
block_typeIEEE registry type: MA-L, MA-M, MA-S, IAB, or CID, or null if unregistered.
address_capacityThe address capacity of the matched block: how many addresses it covers, not how many devices are actually manufactured or active.
range_start / range_endFirst and last address in the matched block.
registeredWhether the address falls in a block IEEE has assigned.
transmission_typeunicast, multicast, or broadcast, from the I/G bit.
administration_typeuniversally_administered or locally_administered, from the U/L bit.
locally_administeredBoolean form of the same U/L bit.
slap_quadrantIEEE 802c SLAP quadrant when locally administered (null otherwise).
eui64The modified EUI-64 mathematically derived from this MAC, or null for non-unicast addresses. This is a derivation from the input, not confirmation the device uses it.
ipv6_link_localThe IPv6 link-local address SLAAC would derive from this MAC, or null for non-unicast addresses. Same caveat as eui64: derived, not confirmed live.
potentially_randomizedWhether this looks like a privacy-randomized address rather than a factory-assigned one.
randomization_confidencenone, possible, or likely.
vendor_lookup_reliableWhether organization can be trusted as the manufacturer, false for randomized or unregistered addresses.
is_zeroWhether the address is all zeros.
is_broadcastWhether the address is the broadcast address (all ones).
explanationThe same plain-English sentence shown on the website's result page.
is_privateDeprecated: kept for older clients. Use organization === null and vendor_lookup_reliable instead.
matched_prefixThe complete registered prefix actually matched, at its real width: full colon-separated bytes, plus a trailing bare hex nibble when the block isn't byte-aligned (a /28 or /36 match). null when unregistered. Unlike oui (always the first 24 bits of the address), this reflects the block's actual size.
prefix_lengthBit width of the matched block: 24 for MA-L/IAB/CID, 28 for MA-M, 36 for MA-S. null when unregistered.
registrynull when unregistered, otherwise {source, record_type, record_updated_at, database_synced_at}. record_updated_at is always null: IEEE's public feeds don't publish a per-record update date. database_synced_at is when this deployment's own copy last synced.
device{category, possible_categories, confidence, inference_source, exact_model_known}. Inferred from a small, manually curated vendor→category dataset, not from IEEE data (which has no product-category field at all). category is "unknown" and confidence is "none" for any organization not in that dataset, which is most of the registry today. exact_model_known is always false: a MAC address alone never identifies an exact model.
virtualization{detected, platform, confidence, signals}. "exact" confidence only for prefixes IEEE itself registered to a hypervisor vendor (VMware, Xen, Hyper-V, VirtualBox, Parallels); lower confidence for conventions that aren't IEEE-registered assignments (QEMU/KVM's libvirt default, Docker's bridge driver). A locally administered address alone is never enough on its own, see randomization.
special_use{detected, type, protocol, name, source, confidence}. Reserved/protocol addresses (broadcast, IPv4/IPv6 multicast mapping, VRRP, HSRP, STP, LACP, 802.1X, LLDP, and a few others), each with the IEEE/IETF/vendor source it's documented in. All fields null and confidence "none" when nothing matches.
vendor_location{raw, address_line, city, region, postal_code, country_code, country_name, parse_confidence}. Every component beyond raw is nullable; only the minority of addresses with a clean comma-delimited shape get a full breakdown ("medium" confidence). The common space-only shape yields just raw/country_code at "low" confidence rather than guessing.
randomization{potentially_randomized, confidence, signals, alternative_explanations}. Machine-readable version of the top-level potentially_randomized/randomization_confidence fields, cross-checked against virtualization (e.g. a locally administered address matching a known hypervisor prefix lists virtual_machine/container as alternative explanations, not just randomization).
local_vendor_derivation{detected, method, universal_mac, confidence, organization, country, matched_prefix, block_type, lookup_url, device, signals}. Best-effort vendor recovery for a locally administered address that looks formed by setting the U/L bit on a real IEEE assignment (multi-BSSID Wi-Fi APs, Wi-Fi Direct interfaces, naive MAC spoofing). detected is true only when clearing that bit lands on a registered prefix and the address isn't a registered, protocol, or all-zero-suffix one. confidence is always "low". This is never a decode of OS privacy randomization, whose bits carry no vendor prefix.
assignmentnull when unregistered, otherwise {registered_at, first_seen_at, last_changed_at}. registered_at is always null (no authoritative IEEE registration date exists). first_seen_at/last_changed_at are this deployment's own sync history, not IEEE dates, so don't treat either as the age of a device using this prefix.
vendornull when unregistered, otherwise {id, registered_name, canonical_name, slug, block_count, lookup_url}. id/slug are a mechanical slug of the exact registered name, never a merge of spelling variants, subsidiaries, or acquisitions of the same real-world company.
meta{request_id, api_version, database_version, processed_at, cached}, present on every successful response (and request_id alone on error bodies). Mirrors the X-Request-Id/X-Data-Version response headers.