Reference
API documentation.
Three endpoints, plain JSON, no SDK required. Everything below works today against https://api.macadress.com. Machine-readable: OpenAPI 3.1 spec.
matched_prefix, registry, device, virtualization, special_use, vendor_location, randomization, assignment, vendor, and meta. Every one of these is new since the original v1 shape shipped, and every one is nullable or safely defaulted: no existing field was removed, renamed, or changed in meaning, so an integration reading only the original fields keeps working unchanged.
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.
| 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/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": [] },
"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.
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/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 — 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). |
| 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 — 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. |