UUIDs are amazing — until someone puts them in a public API like they're a security token or a substitute for good design.
This is Part 2 of our ongoing "What not to do with UUIDs" series. We’re leaving the database trenches behind and heading into the wild world of API design, where UUIDs are often misunderstood, misused, and mistaken for magic.
Here are the most facepalm-worthy antipatterns I've encountered — and what you should do instead.
😬 Antipattern 1: “UUIDs Are Secure Because They're Long”
The scenario:
A password reset flow uses a UUIDv4 in a link:
/reset-password/2cf92d3e-3814-4422-bd11-93e15a601cdcThe belief: “This UUID is random, so users can’t guess it.”
Reality:
- UUIDv4 is not encrypted
- UUIDv1 can be guessable if timestamps and MAC addresses are exposed
- URLs can be leaked via logs, analytics, browser history
✅ Fix:
- Use a secure random token (
crypto.randomBytes,secrets.token_urlsafe) - Store a hashed token in the database
- Include expiry and user binding
> UUIDs are not secrets — they're just IDs.
🌀 Antipattern 2: Accepting UUIDs in Every Format
The scenario:
Your API accepts UUIDs in these forms:
550e8400-e29b-41d4-a716-446655440000550e8400e29b41d4a716446655440000550E8400-E29B-41D4-A716-446655440000- Base64-encoded, because... why not?
Clients start sending all of them. Your backend starts converting, guessing, and eventually breaks when parsing fails.
✅ Fix:
- Standardize the UUID format in your API spec
- Accept lowercase, hyphenated format (
xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx) - Validate UUIDs on input using strict regex or UUID parser libs
> Ambiguity is the enemy of robust APIs.
🙈 Antipattern 3: UUIDs With No Meaning or Labels
The scenario:
A response looks like:
{
"id": "550e8400-e29b-41d4-a716-446655440000"
}Great... but what is this ID? A user? A project? An invoice?
Now imagine this in a list of 10 different resources — all with id.
✅ Fix:
- Use resource-type prefixes or contextual field names:
{
"user_id": "uuid...",
"project_id": "uuid..."
}- Even better: include object types in nested responses
{
"user": {
"id": "uuid...",
"name": "Jane"
}
}> UUIDs are only useful if clients understand what they refer to.
🔁 Antipattern 4: Using UUIDs Without Pagination or Filtering
The scenario:
You fetch 5,000 results with UUIDs and zero filtering:
GET /ordersThe server returns:
[
{ "id": "uuid1", ... },
{ "id": "uuid2", ... },
...
]Pagination? Sorting? Nope.
✅ Fix:
- Paginate with UUIDs using
?after=uuid - Sort with
created_atoruuidv7-style sortable IDs - Offer filtering by UUIDs (
?id=uuid1,uuid2)
> UUIDs aren’t naturally ordered — you need to guide clients.
🔒 Antipattern 5: Relying on UUIDs for Authorization
The scenario:
GET /users/550e8400-e29b-41d4-a716-446655440000The backend fetches and returns the user — no auth checks, assuming the UUID is "unguessable."
Spoiler: it's not.
✅ Fix:
- Always check that the authenticated user has access to the UUID
- Never assume “random = secure”
- Use contextual authorization — not obscurity
> Security through obscurity is not security. It’s tech theater.
😱 Antipattern 6: Logging UUIDs Without Caution
The scenario:
You log every UUID in every API request:
GET /users/uuid - [user_uuid=uuid] - auth_token=...Guess what: now UUIDs are in your logs, your analytics tools, your monitoring dashboards. If you use them as identifiers or keys, they’re now... public.
✅ Fix:
- Mask or redact UUIDs in logs if they're sensitive
- Don’t log them next to auth tokens or emails
- Rotate UUIDs if exposed (in the rare case you used them as secrets)
🔁 Bonus Antipattern: UUIDs in PATCH with No Schema Validation
Clients sometimes send PATCH requests like:
{
"id": "uuid-not-even-valid-1234"
}The server accepts it blindly, stores garbage, and breaks on the next query.
✅ Fix:
- Validate all incoming UUIDs
- Reject malformed UUIDs with 400
- Use a real schema validator (
zod,pydantic,Joi, etc.)
Final Thoughts
UUIDs are powerful. But they’re not magical.
They don’t make APIs secure by default. They don’t replace authorization checks. And they definitely shouldn’t be used as blind tokens in public URLs.
Use them for what they’re great at: uniqueness, global traceability, and distributed consistency.
But when designing your APIs, remember:
> A UUID is just a string — and a poor substitute for security, structure, or sensible design.
🙈 Part 3 is coming soon: "Front-End Fails and UX Nightmares"
