UUID Validation in Python — Versions, Variants, and Common Pitfalls
UUIDs power database primary keys, distributed system coordination, and API idempotency tokens — but not all 128-bit identifiers are created equal. Here's how to validate them properly, detect the version and variant, and avoid the traps that regex alone cannot catch.
In this guide
1. Why UUID validation matters
A Universally Unique Identifier (UUID) is a 128-bit label used to identify resources across systems without coordination. UUIDs appear as database primary keys (PostgreSQL's uuid type), distributed system node identifiers, message deduplication keys, and API idempotency tokens.
Accepting an invalid UUID can corrupt your data layer, cause silent lookup failures, or break downstream services that expect a specific version. In event-driven architectures, a malformed idempotency key can lead to duplicate processing or lost events.
2. UUID versions explained
Not all UUIDs are random. Each version encodes different information and is suited for different use cases:
| Version | Name | How it works |
|---|---|---|
| v1 | Timestamp + MAC | Combines a 60-bit timestamp with the node's MAC address. Sortable by time but leaks hardware identity. |
| v3 | MD5 namespace | Deterministic hash of a namespace UUID + name using MD5. Same input always produces the same UUID. |
| v4 | Random | Generated from 122 random bits. Most widely used — simple and collision-resistant. |
| v5 | SHA-1 namespace | Like v3 but uses SHA-1 instead of MD5. Preferred for new namespace-based IDs. |
| v6 | Reordered time | Like v1 but with the timestamp bits reordered for natural sortability. Draft in RFC 9562. |
| v7 | Unix timestamp + random | Combines a 48-bit Unix millisecond timestamp with random bits. Sortable and privacy-friendly. |
3. The anatomy of a UUID
A UUID is displayed as 32 hexadecimal digits in the canonical 8-4-4-4-12 format, separated by hyphens:
Version nibble
The 13th hex digit (first nibble of the third group) encodes the UUID version. For a v4 UUID it is always 4, for v7 it is 7, and so on. The version ranges from 0 to 15.
Variant bits
The 17th hex digit (first nibble of the fourth group) encodes the variant. For the standard RFC 4122 variant, this nibble is 8, 9, a, or b. Other values indicate NCS backward compatibility, Microsoft GUIDs, or reserved-for-future variants.
4. Why regex isn't enough
The most common approach to UUID validation in Python is a regex like this:
import re # ❌ Matches the format but ignores version and variant UUID_REGEX = re.compile( r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$", re.IGNORECASE, )
Python's standard library also provides uuid.UUID(), which parses UUIDs but does not validate version or variant semantics:
import uuid # ⚠️ Parses successfully but doesn't reject invalid versions parsed = uuid.UUID("550e8400-e29b-f1d4-a716-446655440000") # version nibble = f print(parsed.version) # Raises ValueError for non-standard versions in some Python builds
Both approaches miss critical semantic checks:
Valid format, wrong version
The string 550e8400-e29b-f1d4-a716-446655440000 matches the regex but has version nibble f (15), which is not a standard UUID version. If your system requires v4 UUIDs, the regex will happily accept this invalid input.
No variant checking
Microsoft GUIDs use a different variant encoding than RFC 4122 UUIDs. A regex cannot distinguish between the two. If your database column expects RFC 4122 UUIDs, accepting a Microsoft-variant GUID can cause interoperability issues.
Version-specific enforcement
If your API contract specifies "v4 UUID only" (e.g. for idempotency keys), you need to verify both the format and the version nibble. A regex that tries to encode all version rules becomes unmaintainable.
5. The right solution — IsValid API
The IsValid UUID API validates the format, detects the version (0-15) and variant (RFC 4122, NCS, Microsoft, or future), and optionally enforces a specific version — all in a single call.
Pass any string to the API and get back whether it is a valid UUID, which version it is, and which variant it uses. You can also filter by a specific version to reject UUIDs that don't match your requirements.
Full parameter reference and response schema: UUID API docs →
6. Python code example
Using the isvalid-sdk Python SDK or the popular requests library. Install either with pip install isvalid-sdk or pip install requests.
# validate_uuid.py import os from isvalid_sdk import IsValidConfig, create_client iv = create_client(IsValidConfig(api_key=os.environ["ISVALID_API_KEY"])) # ── Basic validation ───────────────────────────────────────────────────────── result = iv.uuid("550e8400-e29b-41d4-a716-446655440000") print(result["valid"]) # True print(result["version"]) # 4 print(result["variant"]) # 'rfc4122' # ── Enforce a specific version ─────────────────────────────────────────────── v7 = iv.uuid("01932c07-209c-7f43-b572-6a8270527ca5", version=7) if not v7["valid"]: print("Not a valid v7 UUID")
In a FastAPI dependency, validate incoming UUIDs before they reach your database:
# dependencies.py (FastAPI) from fastapi import Depends, HTTPException, Path async def validated_uuid( resource_id: str = Path(..., description="Resource UUID"), ) -> str: """Validate that the path parameter is a valid v4 UUID.""" result = iv.uuid(resource_id, version=4) if not result["valid"]: raise HTTPException( status_code=400, detail="Invalid resource ID — expected a valid v4 UUID", ) return resource_id # Usage in a route: # @app.get("/resources/{resource_id}") # async def get_resource(resource_id: str = Depends(validated_uuid)): # ...
version parameter to enforce that incoming IDs match the UUID version your system generates. This catches copy-paste errors and malicious inputs early — before they hit your database.7. cURL example
Validate a UUID from the command line:
curl -G -H "Authorization: Bearer YOUR_API_KEY" \ --data-urlencode "value=550e8400-e29b-41d4-a716-446655440000" \ "https://api.isvalid.dev/v0/uuid"
Enforce a specific version (returns valid: false if the UUID is not v7):
curl -G -H "Authorization: Bearer YOUR_API_KEY" \ --data-urlencode "value=550e8400-e29b-41d4-a716-446655440000" \ --data-urlencode "version=7" \ "https://api.isvalid.dev/v0/uuid"
8. Understanding the response
Valid v4 UUID:
{ "valid": true, "version": 4, "variant": "rfc4122" }
Invalid UUID (wrong format):
{ "valid": false, "version": null, "variant": null }
| Field | Type | Description |
|---|---|---|
| valid | boolean | Whether the input is a structurally valid UUID (and matches the requested version, if specified) |
| version | number | Detected UUID version (0-15), extracted from the version nibble |
| variant | string | Variant: rfc4122, ncs, microsoft, or future |
9. Edge cases
Nil UUID
The nil UUID 00000000-0000-0000-0000-000000000000 is a valid UUID with version 0 and variant ncs. It is sometimes used as a placeholder or default value. The API will return valid: true with version: 0.
nil_result = iv.uuid("00000000-0000-0000-0000-000000000000") # {"valid": True, "version": 0, "variant": "ncs"}
Max UUID
The max UUID ffffffff-ffff-ffff-ffff-ffffffffffff (RFC 9562) has all bits set to 1. It is valid and has version 15 with variant future. It is used as an upper bound sentinel in range queries.
Case sensitivity
RFC 9562 states that UUIDs should be output as lowercase but accepted case-insensitively. The API accepts both 550E8400-E29B-41D4-A716-446655440000 and its lowercase equivalent as valid.
Braces and URN format
Some systems wrap UUIDs in braces ({550e8400-...}) or use URN notation (urn:uuid:550e8400-...). Strip these wrappers before passing the value to the API — it expects the bare 32-hex-digit format with hyphens.
10. Summary
See also
Validate UUIDs instantly
Free tier includes 100 API calls per day. No credit card required. Detects all UUID versions and variants defined by RFC 9562.