Python library

macadress.com for Python.

A typed Python 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. Sync and async, one dependency.

On PyPI: macadress. Python 3.10+, MIT licensed, one runtime dependency (httpx). Ships type information (py.typed). Source at github.com/sapisos/macadress-python.

Install

pip install macadress

Look up a vendor

The name-only lookup needs no API key:

from macadress import Client

client = Client()  # keyless

client.vendor("00:03:93:AB:12:34")   # "Apple, Inc."
client.vendor("02:1a:2b:3c:4d:5e")   # None (unregistered, private, or randomized)

Full analysis

Everything else needs a free API key. Results are typed dataclasses:

client = Client("mk_live_xxx")

r = client.lookup("3C:22:FB:12:34:56")

r.organization              # str | None
r.country                   # "US" | None
r.block_type                # BlockType.MA_L (compares equal to "MA-L")
r.potentially_randomized    # bool
r.randomization_confidence  # RandomizationConfidence.NONE | POSSIBLE | LIKELY
r.eui64                     # "3E:22:FB:FF:FE:12:34:56" | None
r.device.category           # DeviceCategory.UNKNOWN (usually)
r.explanation               # plain-English summary

# anything without a typed attribute is still reachable
r.get("vendor_location.city")
r.raw

Batch and directory search

for item in client.batch(["00:03:93:00:00:00", "3C:22:FB:00:00:00", "bad"]):
    if item.failed:
        print(item.input, "->", item.error)
    else:
        print(item.input, "->", item.organization)

result = client.search_vendors("Cisco", country="US", limit=20)
for block in result:
    print(block.organization, block.country)

batch() raises ValueError without making a request if the iterable is empty or has more than macadress.MAX_BATCH_SIZE (100) entries.

Async

AsyncClient mirrors Client method for method:

import asyncio
from macadress import AsyncClient

async def main():
    async with AsyncClient("mk_live_xxx") as client:
        print(await client.vendor("00:03:93:AB:12:34"))
        r = await client.lookup("3C:22:FB:12:34:56")
        print(r.organization)

asyncio.run(main())

Errors

Every failure is a MacadressError: InvalidMACError (400), AuthenticationError (401), RateLimitError (429, with .retry_after), QuotaExceededError, TransportError for a network failure, ConfigurationError for bad options. Each carries .status_code, .request_id and .body.

from macadress import Client, RateLimitError, MacadressError

try:
    r = client.lookup(value)
except RateLimitError as exc:
    time.sleep(exc.retry_after or 5)
except MacadressError as exc:
    log.warning("macadress %s: %s (%s)", exc.status_code, exc, exc.request_id)

Quota

Each lookup(), batch() address, and search_vendors() 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