intermediate·9 min read·Updated May 1, 2026

RESTful Best Practices: Conventions That Make APIs Predictable

Fifteen conventions that make REST APIs a joy to use — and the absence of any one is a smell.

Why conventions matter

A "RESTful" API is predictable. If a developer has used three of your endpoints, they can guess how the fourth works. That predictability compounds — each new endpoint takes less time to understand, test, and integrate with.

The fifteen conventions below aren't rules. They're defaults that work. Deviate when you have a reason; follow them when you don't.

1. Use plural nouns for collections

  • GET /users, GET /users/123
  • GET /user, GET /getUser/123

Plural nouns scale cleanly to both collection and single-resource URLs.

2. Nest resources only when the relationship is strong

  • GET /users/123/orders (an order inherently belongs to a user)
  • GET /users/123/products/456 (products exist independently)

Deep nesting (three levels or more) almost always hurts. Prefer flat URLs with query parameters.

3. Use HTTP methods, not verbs in URLs

  • DELETE /users/123
  • POST /users/123/delete, GET /deleteUser?id=123

4. Use standard status codes

Don't invent codes. If the request succeeded, return 200, 201, or 204. If the client messed up, 4xx. If you messed up, 5xx. See the status codes lesson.

5. Return consistent error envelopes

Pick one error shape and use it everywhere:

{
  "success": false,
  "error": "VALIDATION_ERROR",
  "message": "Email is not valid",
  "details": [{ "field": "email", "code": "invalid_format" }]
}

Inconsistent error shapes are the #1 thing that breaks automated tests against an API.

6. Version via URL prefix

  • /api/v1/users
  • ✗ Custom header versioning, content-type versioning (too clever, too hard to test)

Start at v1 even if it's your first version. You'll need v2 eventually.

7. Use ISO 8601 for timestamps

  • 2026-05-01T14:30:00Z
  • ✗ Unix epochs (hard for humans), US-locale strings (ambiguous)

Always UTC. Let the client convert to the user's timezone.

8. Use UUIDs, not auto-increment IDs, where possible

Auto-increment exposes customer counts, creates guessability vulnerabilities, and makes database merges painful. UUIDs are 36 characters of don't-think-about-it.

9. Paginate every list endpoint

Every list endpoint. No exceptions. Even if you think "there'll only be a few." Covered in detail in query parameters and pagination.

10. Return the resource after writes

POST, PUT, PATCH should all return the full resource in the response. Saves the client a follow-up GET. The exception: DELETE, which returns 204 No Content.

11. Support idempotency keys on POST

A client-supplied Idempotency-Key header lets clients retry POSTs safely: if the server sees the same key, it returns the cached response instead of creating a duplicate. Stripe popularized this pattern and it's now widely adopted.

12. Rate-limit, and communicate it clearly

Return x-ratelimit-limit, x-ratelimit-remaining, and x-ratelimit-reset on every response. When a client hits the limit, return 429 with a Retry-After header.

13. Use CORS correctly

If browsers should call your API directly, send Access-Control-Allow-Origin with specific origins, not *. Don't enable CORS for endpoints that handle cookies or auth unless you fully understand CSRF implications.

14. Document with OpenAPI

OpenAPI (formerly Swagger) is the de facto standard for describing REST APIs. A machine-readable spec unlocks:

  • Auto-generated client SDKs
  • Auto-generated test suites (what ShiftLeft does)
  • Interactive documentation (Swagger UI, ReDoc)
  • Contract testing

No OpenAPI = you're manually writing docs, SDKs, and tests that all drift out of sync. In 2026 this is inexcusable.

15. Keep the response envelope predictable

Pick a shape and use it everywhere. Two common patterns:

Envelope-wrapped:

{ "success": true, "data": {...}, "meta": {...} }

Raw:

{ "id": "...", "name": "...", ... }

Either works. Mixed does not. Testers spend hours writing conditional assertions because some endpoints wrap in { data: ... } and others don't.

Bonus: deprecate gracefully

When you remove a field or endpoint:

  1. Announce the deprecation at least 90 days in advance.
  2. Send a Deprecation header with the date.
  3. Keep the old behavior working through the deprecation window.
  4. Log usage so you can warn the last remaining clients before pulling it.

What's next

You've seen the conventions. Now dig into GraphQL — the modern alternative to REST that solves over-fetching — or jump to authentication.

Related lessons

Read more on the blog