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#).
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:
| Method | How |
|---|---|
| Header | Works for every endpoint: Authorization: Bearer mk_... |
| Query param | Works for every endpoint: ?api_key=mk_... |
| POST body | Batch 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.
| Plan | Requests / minute |
|---|---|
| Free | 30 |
| Growth | 120 |
| Scale | 600 |
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.
| Plan | Lookups / cycle | Grace | Paused at |
|---|---|---|---|
| Free | 1,000 | — | 1,000 |
| Growth | 100,000 | 20% | 120,000 |
| Scale | 1,000,000 | 20% | 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:
| Header | Meaning |
|---|---|
| X-Request-Id | Per-request identifier. Matches the request_id in the JSON body on a successful lookup. |
| X-Data-Version | Date (UTC) the underlying vendor database last synced successfully, e.g. 2026-08-22. Same value as meta.database_version. |
| X-RateLimit-Limit | Requests-per-minute cap for your plan. |
| X-RateLimit-Remaining | Requests left in the current sliding window. |
| X-RateLimit-Reset | Seconds 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.
/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:
| Body | Meaning |
|---|---|
not registered | The prefix isn't in the current IEEE registry snapshot, or the block is private. |
locally administered | A 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.
/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"}
/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.
/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.
/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"
| Param | Meaning |
|---|---|
query | Optional. Substring match against organization name, case-insensitive. |
country | Optional. Exact ISO 3166-1 alpha-2 code, e.g. US, DE, JP. |
limit | Optional. 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.
/v1/healthz
Returns 200 {"status": "ok"} when the database is reachable, 503 otherwise. For uptime checks, not counted against any quota.
Response fields
| Field | Meaning |
|---|---|
| mac | The address, normalized to canonical colon-separated uppercase form. |
| valid | Whether the input parsed as a MAC address at all. |
| oui | The top 24 bits (first three octets), colon-separated. |
| organization | Registered vendor name, or null if unregistered or the block is marked private by IEEE. |
| vendor_address | The organization's registered address, or null. |
| country | ISO 3166-1 alpha-2 code extracted from the vendor address, or null. |
| block_type | IEEE registry type: MA-L, MA-M, MA-S, IAB, or CID, or null if unregistered. |
| address_capacity | The address capacity of the matched block: how many addresses it covers, not how many devices are actually manufactured or active. |
| range_start / range_end | First and last address in the matched block. |
| registered | Whether the address falls in a block IEEE has assigned. |
| transmission_type | unicast, multicast, or broadcast, from the I/G bit. |
| administration_type | universally_administered or locally_administered, from the U/L bit. |
| locally_administered | Boolean form of the same U/L bit. |
| slap_quadrant | IEEE 802c SLAP quadrant when locally administered (null otherwise). |
| eui64 | The 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_local | The 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_randomized | Whether this looks like a privacy-randomized address rather than a factory-assigned one. |
| randomization_confidence | none, possible, or likely. |
| vendor_lookup_reliable | Whether organization can be trusted as the manufacturer, false for randomized or unregistered addresses. |
| is_zero | Whether the address is all zeros. |
| is_broadcast | Whether the address is the broadcast address (all ones). |
| explanation | The same plain-English sentence shown on the website's result page. |
| is_private | Deprecated: kept for older clients. Use organization === null and vendor_lookup_reliable instead. |
| matched_prefix | The 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_length | Bit width of the matched block: 24 for MA-L/IAB/CID, 28 for MA-M, 36 for MA-S. null when unregistered. |
| registry | null 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. |
| assignment | null 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. |
| vendor | null 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. |