🐍 PythonIoT / Hardware

IoT Device Identification — IMEI & MAC Address Validation

Validate IMEI numbers and MAC addresses in Python. Verify Luhn checksums, normalise MAC formats, detect multicast and locally-administered addresses, and run parallel checks with asyncio.gather.

Also available in Node.js

1. Validate IMEI

Use await iv.imei(value) (SDK) or GET /v0/imei?value=….

{
  "valid": true,
  "imei": "352099001761481",
  "tac": "35209900",
  "fac": "00",
  "snr": "176148",
  "checkDigit": "1",
  "luhn": true
}
  1. Check valid and luhn
  2. Store tac for device model family tracking
  3. Use snr as unique unit identifier

2. Validate MAC address

Use await iv.mac_address(value) (SDK) or GET /v0/mac-address?value=….

{
  "valid": true,
  "normalized": "00:1B:44:11:3A:B7",
  "format": "colon",
  "type": "unicast",
  "isMulticast": false,
  "isLocal": false,
  "isBroadcast": false
}
  1. Check valid
  2. Store normalized as canonical form
  3. Reject is_multicast and is_broadcast
  4. Warn on is_local — may be randomised
⚠️iOS 14+ and Android 10+ randomise MAC addresses for Wi-Fi scanning by default. A locally-administered MAC (is_local = True) may change across sessions and is unsuitable as a persistent device identifier.

3. Parallel validation with asyncio.gather

import asyncio
from isvalid_sdk import IsValidConfig, create_client

config = IsValidConfig(api_key="YOUR_API_KEY")
iv = create_client(config)

async def validate_device(imei: str | None = None, mac: str | None = None):
    tasks = {}
    if imei: tasks["imei"] = iv.imei(imei)
    if mac:  tasks["mac"]  = iv.mac_address(mac)

    results = dict(zip(tasks.keys(), await asyncio.gather(*tasks.values())))
    warnings = []

    mac_r = results.get("mac")
    if mac_r and mac_r.valid:
        if mac_r.is_multicast:
            warnings.append("MAC is multicast — unlikely for a physical device")
        if mac_r.is_local:
            warnings.append("MAC is locally administered — may be randomised (iOS/Android privacy MAC)")
        if mac_r.is_broadcast:
            raise ValueError("Broadcast MAC is not a valid device identifier")

    return {"results": results, "warnings": warnings}

async def main():
    device = await validate_device(
        imei="352099001761481",
        mac="00:1B:44:11:3A:B7",
    )
    imei_r = device["results"]["imei"]
    mac_r  = device["results"]["mac"]
    print(f"IMEI valid: {imei_r.valid}, TAC: {imei_r.tac}")
    print(f"MAC valid: {mac_r.valid}, normalized: {mac_r.normalized}")
    if device["warnings"]:
        print(f"Warnings: {device['warnings']}")

asyncio.run(main())

4. Edge cases

IMEI with dashes or spaces

💡The API accepts IMEIs with dashes, spaces, or as a plain 15-digit string — all common from device settings.
# All accepted:
await iv.imei("352099001761481")
await iv.imei("35-209900-176148-1")
await iv.imei("352 099 001 761 481")

MAC format normalisation

# All accepted — normalized form is always returned
result = await iv.mac_address("001B44113AB7")     # compact
result = await iv.mac_address("00-1B-44-11-3A-B7")  # hyphen
result = await iv.mac_address("00:1b:44:11:3a:b7")  # lowercase
print(result.normalized)  # "00:1B:44:11:3A:B7" — always uppercase colon form

Batch device validation

device_pairs = [
    {"imei": "352099001761481", "mac": "00:1B:44:11:3A:B7"},
    {"imei": "490154203237518", "mac": "AC:BC:32:A1:B2:C3"},
]

async def validate_all(devices):
    tasks = [validate_device(**d) for d in devices]
    return await asyncio.gather(*tasks, return_exceptions=True)

results = await validate_all(device_pairs)

5. Summary checklist

Validate IMEI Luhn checksum via API
Store TAC for device model family tracking
Store normalized MAC (colon uppercase)
Reject multicast and broadcast MACs
Warn on locally-administered (randomised) MACs
Run IMEI + MAC in parallel with asyncio.gather
Accept IMEI with dashes/spaces from device settings
Return 422 with field-level error messages

See also

Ready to integrate?

Free tier — 1,000 requests/month. No credit card required.

Get your API key →