Almost every developer builds a REST API eventually — and almost every developer has also suffered through using a badly designed one. The difference between an API that’s a joy to work with and one that’s a constant frustration comes down to a handful of well-known conventions. These REST API best practices are the habits that make your API predictable, intuitive, and pleasant for the next person (often future you) to use.
Use nouns for resources, not verbs
A REST API is organized around resources — the things your system manages, like users or orders. Your URLs should name those resources as nouns, and let the HTTP method describe the action. So use /users, not /getUsers or /createUser. The method already says what you’re doing:
GET /users— retrieve usersPOST /users— create a userGET /users/123— retrieve one userPUT /users/123— update that userDELETE /users/123— delete that user
Baking the verb into the URL is redundant and inconsistent; letting the HTTP method carry the action keeps everything clean and guessable.
Use HTTP methods correctly
Each HTTP method has an expected meaning, and honoring it makes your API intuitive. GET should only read data and never change anything. POST creates. PUT and PATCH update (PUT replaces, PATCH modifies part). DELETE removes. Just as important, GET, PUT, and DELETE should be idempotent — calling them repeatedly has the same effect as calling them once. Respecting these semantics means clients, caches, and proxies all behave the way everyone expects.
Return the right status codes
Status codes are how your API communicates outcomes, so use them honestly instead of returning 200 OK for everything. The essentials:
- 2xx — success (
200OK,201Created,204No Content). - 4xx — the client made a mistake (
400Bad Request,401Unauthorized,403Forbidden,404Not Found). - 5xx — the server failed (
500Internal Server Error).
Meaningful status codes let clients handle responses programmatically instead of parsing your prose to guess what happened.
Version your API
Your API will change, and changes can break the apps that depend on it. Versioning lets you evolve without pulling the rug out from existing clients. The common approach is a version in the URL, like /v1/users. When you need breaking changes, you introduce /v2 while keeping /v1 alive for a transition period. Plan for versioning from day one — retrofitting it later is painful.
Handle errors clearly and consistently
When something goes wrong, don’t just return a status code and a blank body. Send a consistent, structured error response that tells the client what happened and, ideally, how to fix it — a machine-readable error code and a human-readable message. Consistency is key: every error across your API should follow the same shape, so clients can handle failures uniformly instead of special-casing each endpoint.
Support pagination, filtering, and sorting
Never return an unbounded list. If /users tries to hand back a million records, you’ll cripple both your server and the client. Instead, paginate results and let callers page through them, and offer query parameters for filtering and sorting — for example /users?status=active&sort=created_at&page=2. This keeps responses fast and gives clients control over exactly what they get.
Secure it and use JSON consistently
A few non-negotiables round things out. Always serve your API over HTTPS so data and tokens can’t be intercepted. Require authentication for anything sensitive, commonly via tokens in the Authorization header. Use JSON as your data format consistently, with predictable field names. And document your API well — good documentation is the difference between an API people adopt happily and one they avoid.
Designing nested resources sensibly
Real APIs have relationships — comments belong to posts, orders belong to users — and nesting URLs expresses that naturally: GET /posts/42/comments reads exactly like what it returns. But nesting has a depth limit of good taste. By the time you’re at /users/7/posts/42/comments/9/replies/3, the URL is brittle and half the IDs are redundant. The pragmatic rule: nest one level to express ownership, then give resources their own top-level address. A comment can live at /comments/9 even though you found it via /posts/42/comments. Shallow URLs stay stable as your data model evolves; deep ones fossilize today’s schema into every client.
A consistent error shape saves everyone hours
It’s worth showing what “structured errors” means concretely. Pick one shape and use it for every failure across your API:
{
"error": {
"code": "validation_failed",
"message": "Email address is not valid.",
"field": "email",
"request_id": "req_8fk2n1"
}
}
Each part earns its place: a machine-readable code that clients can branch on (and that never changes wording), a human-readable message safe to show or log, the offending field for form validation, and a request_id that lets someone paste an identifier into a support ticket and lets you find the exact log line. APIs with this discipline are debuggable by strangers at 2 a.m.; APIs that return a bare 400 with “Bad Request” are not.
Don’t forget rate limiting and idempotency
Two production concerns separate hobby APIs from professional ones. Rate limiting protects you from both abuse and accidental client loops — return 429 Too Many Requests when exceeded, and include headers telling clients their limit and when it resets, so well-behaved clients can pace themselves instead of guessing.
Idempotency keys solve a subtler problem: a client sends POST /payments, the network drops before the response arrives, and the client has no idea whether the payment happened. Retrying might charge twice; not retrying might charge zero times. The fix: let clients send a unique Idempotency-Key header, and if you see the same key again, return the stored result of the first attempt instead of re-executing. For any endpoint with real-world side effects — payments, orders, messages — this pattern is the difference between “reliable” and “duplicate charges in production.”
Frequently asked questions
PUT or PATCH for updates? Semantically, PUT replaces the whole resource while PATCH modifies part of it. In practice, most APIs standardize on PATCH with a partial body because clients rarely want to resend every field. Whichever you pick, be consistent across the API — mixed conventions confuse more than either choice.
Should IDs in URLs be sequential numbers? Prefer opaque IDs (UUIDs or random strings) for anything sensitive. Sequential IDs leak information — competitors can watch your order volume grow — and invite enumeration attacks where someone walks /users/1, /users/2, /users/3 probing your access control.
How should I document my API? Write an OpenAPI (Swagger) specification. It’s the industry-standard machine-readable format, and one spec file generates interactive docs, client libraries, and request validation. Keeping it in version control next to the code it describes is the best habit an API team can adopt.
The takeaway
Great APIs feel obvious to use, and that’s no accident. These REST API best practices — noun-based resource URLs, correct HTTP methods and status codes, versioning, consistent error handling, pagination, and solid security — are the shared conventions that make an API predictable. Follow them and developers can guess how your API works before reading the docs, which is the highest compliment an API can earn.

