API Design Patterns
Reference for REST API design decisions. Covers resource naming, HTTP semantics, request/response conventions, errors, versioning, pagination, and long-running work. Drawn primarily from Google’s API Design Guide (AIP series) and Google Cloud’s RESTful best practices.Agent Trigger
Apply when: Designing or reviewing HTTP/REST endpoints, URL structure, error responses, versioning, or pagination. Rule of thumb: Resource-oriented URLs, correct HTTP semantics, structured error bodies, idempotency keys on writes.Resource Naming
Intent: URLs identify nouns (resources), not verbs or actions. The URL is a stable address; behavior comes from the HTTP method. Rules:- Use plural nouns for collections:
/users,/orders,/books - Hierarchy via path segments:
/users/{userId}/orders/{orderId} - Collection IDs: plural, lowerCamelCase —
users,bookReviews - Resource IDs: server-assigned unless the client explicitly provides one at creation
- Avoid encoding verbs in the URL (
/getUser,/createOrder)
POST /orders/{id}:cancel, POST /documents/{id}:publish. This is the AIP-136 pattern; it keeps the verb out of the base URL while signaling non-standard behavior.
Anti-patterns:
- Verb in URL:
POST /createUser→ usePOST /users - Flat namespace when hierarchy exists:
/userOrders→/users/{id}/orders - Inconsistent plurality: mixing
/userand/orders
HTTP Method Semantics
*PATCH is idempotent only when you use field masks (same mask + same values = same result).
GET: must have no side effects. Never use GET to trigger state changes.
POST: use for creation (
POST /users) and for custom actions that don’t fit standard methods (POST /jobs/{id}:cancel).
PUT vs PATCH: prefer PATCH with a field mask for updates. Reserve PUT for full replacement (rare — clients must send the complete resource or risk losing fields they didn’t include).
DELETE: should be idempotent — deleting an already-deleted resource should return 404 (or 200 with a tombstone for soft delete), not error. If deletion is async, return a long-running operation, not 200.
Request/Response Design
Field naming
- JSON APIs:
snake_caseis common (Python ecosystem, many REST APIs); Google’s guide useslowerCamelCasefor JSON field names (Protobuf JSON mapping). Pick one and be consistent across all endpoints. - Standard field names to reuse (AIP-148):
name— resource’s full resource namedisplay_name— human-readable labelcreate_time,update_time— RFC 3339 timestampslabels— user-defined key/value metadata mapetag— for optimistic concurrencypage_token,next_page_token— for pagination
Partial responses (field masks)
When clients only need a subset of fields, accept afields query param or a read_mask body field:
update_mask to specify which fields to change — everything else is untouched:
"address.city" updates only the nested city field.
Output vs input fields
Mark server-computed fields clearly in docs (AIP-203):create_time, uid, etag are output-only. Clients must not send them on create/update; servers must ignore them if present.
Error Response Shape
Intent: errors must be machine-parseable and human-readable without leaking internals. HTTP status code selection:
Never return 200 with an error body. A 200 means the operation succeeded. Clients parse status first.
Structured error body (AIP-193):
code— HTTP status integerstatus— canonical error name (string enum:NOT_FOUND,INVALID_ARGUMENT,ALREADY_EXISTS, etc.)message— human-readable, actionable, no stack traces or internal IDsdetails— typed error extensions (field violations, retry info, quota info)
- Returning stack traces in
message - Using
error: "something went wrong"with no detail - Returning 500 for all errors, including client errors
- Inconsistent shapes across endpoints
Versioning Strategies
Two main approaches:URL versioning
/v1/). Minor changes are additive and non-breaking — no version bump needed.
Header versioning
- Greenfield public API, wide audience → URL versioning (simpler for consumers)
- Internal API or tight client control → header versioning viable
- Date-based header versioning (Stripe pattern) works well when you want a continuous changelog rather than discrete versions
- Adding a new field to a response
- Adding a new optional request parameter
- Adding a new enum value (with caution — clients must handle unknown values)
- Adding a new resource or method
- Renaming or removing a field
- Changing a field’s type
- Changing URL structure
- Changing error codes or error shape
- Removing an endpoint
Pagination
Cursor-based (preferred)
next_page_tokenis opaque to the client (server encodes position internally)- Absent or empty
next_page_tokenmeans last page - Stable across concurrent writes — cursor points to a position, not an offset
- Required for real-time or high-churn datasets
Offset-based
- Simple to implement and reason about
- Breaks on concurrent inserts/deletes (items can appear twice or be skipped)
- Acceptable for static or low-churn data; avoid for feeds or event streams
- Collection field named after the resource:
"users": [...]not"data": [...] next_page_tokenfor continuation- Optional
total_size(integer) when cheap to compute — omit if it requires a COUNT(*) that kills performance
- Returning all results with no pagination on unbounded collections
- Using page numbers (
page=3) — fragile under insertion dataas the collection key — not self-documenting
Idempotency Keys
Intent: allow clients to safely retry POST (create) requests without creating duplicate resources. Pattern:- Check if key was seen before.
- If yes: return the original response (same status + body), do not re-execute.
- If no: execute and store result keyed by idempotency key.
- Key expiry: typically 24h–7d.
- Key is client-generated (UUID recommended)
- Scope key to the authenticated user to prevent cross-user replay
- Return
409 Conflictif the same key is used with a different request body - Store results durably (DB, not in-memory cache)
Long-Running Operations
When an operation cannot complete within a single HTTP round-trip (~30s), choose a strategy:Polling (LRO pattern — AIP-151)
- Initial POST returns an operation resource immediately
- Client polls
GET /operations/{id}untildone: true - On failure:
done: true+errorfield instead ofresponse - Include
metadatafor progress info (percent complete, ETA) - Support
DELETE /operations/{id}to cancel (:cancelcustom method preferred per AIP-136)
Webhooks / callbacks
- Client registers a callback URL at subscription time
- Server POSTs result to the URL when done
- Requires the client to have a publicly reachable endpoint
- Better for fire-and-forget; worse for request/response flow
Server-sent events / streaming
- Client holds an HTTP connection open; server pushes events as they arrive
- Best for real-time feeds (logs, metrics, chat)
- Not suitable for one-off async operations
API Design Anti-Patterns
Cross-References
- Error Handling Patterns — language-level error propagation and wrapping
- Software Design Principles — SRP (single endpoint responsibility), ISP (client-specific interfaces)
- Distributed Systems — idempotency, consistency guarantees, retry semantics
- System Design Process — where API design fits in the overall system design flow