โ† Back to Blog

Using UUIDs in API Design

2026-04-14 ยท 5 min read

Why Use UUID in APIs

In REST API design, UUID as resource identifiers has clear advantages over auto-increment integer IDs: higher security โ€” does not expose database record count and order, preventing users from enumerating resources via /users/1, /users/2; supports client-side ID generation (idempotent creation) โ€” clients can generate UUIDs before sending POST requests, making creation operations idempotent (retries won't create duplicate records); distribution-friendly โ€” multiple services and nodes can generate IDs independently without conflicts; clearer URL semantics โ€” UUID in a URL clearly identifies a specific resource, not a potentially changing sequence number.

REST API Path Design Conventions

# ๆŽจ่็š„ UUID ่ทฏๅพ„ๆ ผๅผ๏ผˆๅฐๅ†™๏ผŒๅธฆ่ฟžๅญ—็ฌฆ๏ผ‰
GET    /api/v1/users/550e8400-e29b-41d4-a716-446655440000
PUT    /api/v1/users/550e8400-e29b-41d4-a716-446655440000
DELETE /api/v1/users/550e8400-e29b-41d4-a716-446655440000

# ๅตŒๅฅ—่ต„ๆบ
GET    /api/v1/users/550e8400-e29b-41d4-a716-446655440000/orders
GET    /api/v1/orders/9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d

# ๅน‚็ญ‰ๅˆ›ๅปบ๏ผˆๅฎขๆˆท็ซฏๆไพ› UUID๏ผ‰
PUT    /api/v1/resources/client-generated-uuid
# ๆœๅŠก็ซฏๅฆ‚ๆžœๅทฒๅญ˜ๅœจ่ฏฅ UUID๏ผŒๅฏไปฅ่ฟ”ๅ›ž 200 ๆˆ– 409

# API ๅ“ๅบ”ๆ ผๅผ่ง„่Œƒ๏ผˆ็ปŸไธ€ไฝฟ็”จๅญ—็ฌฆไธฒ๏ผ‰
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "name": "Example User",
  "createdAt": "2025-01-01T00:00:00Z"
}

Format Normalization Recommendations

Recommendations for UUID format in APIs: accept input loosely (accept both upper/lowercase, with or without braces), output strictly (always return in lowercase without braces format); in path parameters, validate format and return 400 Bad Request for invalid format rather than 404 Not Found (tells callers it's a format issue, not "resource not found"); in JSON body, transmit UUID as string (don't attempt numeric representation, which loses precision); in API documentation, clearly specify using UUID v4 and provide format examples.

Error Handling Best Practices

# FastAPI ็คบไพ‹๏ผšUUID ๅ‚ๆ•ฐ้ชŒ่ฏๅ’Œ้”™่ฏฏๅค„็†
from uuid import UUID
from fastapi import FastAPI, HTTPException, Path
from fastapi.responses import JSONResponse

app = FastAPI()

@app.exception_handler(ValueError)
async def uuid_exception_handler(request, exc):
    return JSONResponse(
        status_code=400,
        content={
            "error": "invalid_uuid",
            "message": "The provided ID is not a valid UUID format",
            "expected": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
        }
    )

@app.get("/api/users/{user_id}")
async def get_user(
    user_id: UUID = Path(..., description="User UUID in RFC 4122 format")
):
    user = db.find_user(user_id)
    if not user:
        raise HTTPException(
            status_code=404,
            detail={
                "error": "not_found",
                "message": f"User {user_id} not found"
            }
        )
    return user

Idempotency and Retry Safety

UUID is very useful in API idempotency design. Standard pattern: the client generates a UUID as the Idempotency-Key request header before the first request; the server caches this UUID with the request result (typically in Redis for 24 hours); if the same Idempotency-Key is requested again, directly return the cached response without re-executing the operation; this is particularly important for operations that cannot be repeated, like payments and order creation. Payment platforms like Stripe and Adyen use this pattern, allowing clients to safely retry failed requests without side effects.

# ๅน‚็ญ‰ API ่ฐƒ็”จ็คบไพ‹
import uuid
import httpx

def create_order_idempotent(order_data, max_retries=3):
    idempotency_key = str(uuid.uuid4())  # ๆœฌๆฌก่ฐƒ็”จๅ…จๅฑ€ๅ”ฏไธ€

    for attempt in range(max_retries):
        try:
            response = httpx.post(
                '/api/orders',
                json=order_data,
                headers={'Idempotency-Key': idempotency_key}
            )
            if response.status_code in (200, 201):
                return response.json()
            elif response.status_code == 409:
                return response.json()  # ๅน‚็ญ‰๏ผš่ฟ”ๅ›žๅทฒๅญ˜ๅœจ็š„็ป“ๆžœ
        except httpx.NetworkError:
            if attempt == max_retries - 1:
                raise
            # ไฝฟ็”จ็›ธๅŒ็š„ idempotency_key ้‡่ฏ•

UUID Usage in GraphQL

# GraphQL schema ็คบไพ‹
scalar UUID

type User {
  id: UUID!          # ้ž็ฉบ UUID ็ฑปๅž‹
  name: String!
  email: String!
  posts: [Post!]!
}

type Query {
  user(id: UUID!): User
  users: [User!]!
}

type Mutation {
  createUser(input: CreateUserInput!): User!
  updateUser(id: UUID!, input: UpdateUserInput!): User!
  deleteUser(id: UUID!): Boolean!
}

# ๆŸฅ่ฏข็คบไพ‹
query {
  user(id: "550e8400-e29b-41d4-a716-446655440000") {
    id
    name
    email
  }
}

Try the free tool now

Use Free Tool โ†’