Using UUIDs in APIs: IDs, Collisions, and Best Practices
When to use UUIDs as API identifiers, v4 and v7 trade-offs, database indexing, validation, and security pitfalls in production systems.
By Rehan Baqir
A UUID is a 128-bit identifier you can mint without asking a database for the next integer. That is the reason to use one in an API. The limits show up immediately: a UUID is larger than an integer, random v4 values scatter B-tree inserts, and the ID does not authenticate the caller. Generate fixtures with the UUID generator. Check shapes in the regex tester and the JSON formatter. Base64 encode moves binary payloads. It does not create IDs or make them harder to guess.
Sequential paths leak volume. A competitor scraped a staging API and read launch volume off /orders/10482 and /orders/10483. Moving those external identifiers to UUIDs removed the sequence. Random v4 primary keys then scattered inserts across B-tree pages, and the index work took a week. The ID format is a product choice with a database cost. Bit layout is covered in how UUIDs work.
What a UUID is in an API
The value is usually written as 36 characters with hyphens:
550e8400-e29b-41d4-a716-446655440000
Standards define versions 1 through 8. APIs most often use v4 (random) and, more recently, v7 (timestamp-ordered).
Clients and servers can create these IDs without a central allocator. That fits distributed systems, offline-first mobile apps, and public paths that should not reveal order or volume.
UUIDs and auto-increment integers
| Approach | Pros | Cons | |----------|------|------| | Integer ID | Small, fast indexes, easy debugging | Predictable, exposes count, shard merge pain | | UUID | Globally unique, opaque, shard-friendly | Larger keys, index fragmentation (especially random v4) | | ULID / Snowflake | Sortable, compact text | Extra dependency, less universal than UUID |
A public path such as /orders/550e8400-e29b-41d4-a716-446655440000 does not show how many orders exist. Internal joins can still use an integer if you keep both columns.
UUID v4: random
Version 4 fills most bits with random data. The version and variant bits are set according to the spec. Collision probability is negligible in ordinary deployments when the source is cryptographic random, and Math.random() in a loop is not that source.
Use v4 when the ID should be opaque and insert order in the index does not matter at your volume.
Random UUID primary keys can split index pages in PostgreSQL, MySQL, and similar engines under heavy inserts. The usual mitigations are v7, a sequential internal ID plus a separate public UUID column, or a BRIN index on a time-correlated column.
Generate test values with the UUID generator while you build fixtures.
UUID v7: time-ordered
Version 7 puts a Unix timestamp in the leading bits. Inserts sort more naturally, B-tree locality improves, and the value is still opaque enough for many public URLs. New services in 2026 often default to v7 for primary keys when log lines need to line up with creation time.
Use v7 when you want UUID generation plus roughly time-ordered inserts, and when ORDER BY id should approximate creation time without a separate created_at index.
v7 exposes a coarse creation time. That is usually acceptable on a resource ID. It is a poor fit when the timestamp itself has to stay hidden.
API design patterns
Resource paths:
GET /api/orders/550e8400-e29b-41d4-a716-446655440000
PATCH /api/users/7c9e6679-7425-40de-944b-e07fc1f90ae7
Accept the UUID string in the path. Validate the format before the database lookup so you avoid cast errors and timing differences.
Client-generated IDs: a mobile or offline-first app creates the UUID locally and syncs later, with no round trip to wait for a server assignment. Document that clients must send standard v4 or v7, and reject custom strings such as order_12345.
Correlation IDs: one UUID per HTTP request ties log lines together across microservices. The string looks like an entity ID and means something else. Reusing an entity ID as a trace ID in a security-sensitive audit log needs an explicit policy.
Bulk create: POST /api/items/batch may accept a client UUID per row so a retry stays idempotent. The server should reject duplicates inside the batch and return 409 when the primary key already exists.
Validation and parsing
Reject a malformed ID early:
- 32 hex digits, hyphens in the 8-4-4-4-12 positions
- Case-insensitive hex (
a-f,A-F) - JSON APIs do not require braces (some Windows tooling does)
Test patterns in the regex tester. Prefer a language or database parser (uuid.Parse in Go, the UUID type in PostgreSQL) when you have one.
Do not cast an unknown string to a UUID type until it has passed validation. If the difference between a malformed ID and a missing record would reveal another tenant's data, use one generic message, such as "Invalid request", for both.
Storage
- Store a native UUID type (PostgreSQL
uuid, SQL Serveruniqueidentifier) orBINARY(16). AVARCHAR(36)column costs space and skips the format check. - Index UUID columns that joins use. A missing index turns the join into a full scan.
- Lowercase hex in JSON responses is a common convention. Pick one casing and keep it, because clients use the string as a cache key.
- Skip braces (
{550e8400-...}) in API storage unless a legacy client requires them.
Serialization
JSON uses strings:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"customer_id": "7c9e6679-7425-40de-944b-e07fc1f90ae7"
}
Some drivers return binary. Convert that to canonical hyphenated hex before it leaves the API. A mix of 550e8400e29b41d4a716446655440000 and the hyphenated form splits client caches and breaks string equality.
Inspect payloads with the JSON formatter during integration tests.
Security
A UUID identifies a record. Knowing an order UUID must not grant access. Enforce ownership, scopes, and session tokens on every request.
A UUID used as a session token still needs extra entropy, rotation, and expiration. Use a session library built for that job.
Base64 encode transports binary payloads. It does not generate IDs or strengthen them.
Rate-limit enumeration on sensitive resources even when the IDs are UUIDs. An opaque ID can still be walked if authorization is missing.
Logging and privacy
A UUID on its own is generally not personal data. Combined with timestamps and IP logs, it traces a journey. Redact or hash IDs in public error reports when a support bundle leaves your network.
Testing and fixtures
Copy values from the UUID generator for unit tests, or seed deterministic UUIDs in factories. One hardcoded UUID reused in every test hides integration bugs between services that assume each ID is unique.
When an integer or another ID fits better
- Internal surrogate keys, where an integer is enough and the URL is never exposed
- Support codes a person has to read aloud (
ORDER-4829), or short slugs - High-volume time-series, where Snowflake or ULID can beat random UUID index behavior
- SMS deep links, where URL length matters. A UUID is 36 characters at minimum
Common mistakes
Treating the UUID as authorization. Anyone with the link can open the resource until auth middleware checks ownership.
v4 primary keys at billions of rows. Watch index bloat, and plan a v7 or hybrid scheme before the table gets there.
Client-supplied IDs with no uniqueness constraint. A unique index on id is mandatory.
Several UUID versions in one column with no rule. Document the versions you accept. Reject v1 if the privacy policy forbids a MAC address inside the ID.
Related articles
- How UUIDs work: bit layout and version differences
- Understanding regular expressions: pattern basics for validation
- JSON formatting guide: reading API payloads
What you plan for
UUIDs give you distributed ID generation and public URLs that hide sequence and volume. The keys are larger, and the index behavior has to be chosen on purpose. Pick v4 or v7 for a stated reason, validate every inbound string, and keep authorization independent of the ID's shape. Generate test IDs with the UUID generator while the schema still fits on one whiteboard.
Frequently Asked Questions
Common questions answered to help you get the most from this tool.