REST API design best practices: URLs, status codes and errors
A good API is predictable, consistent and hard to misuse. These conventions make yours easy to use and easy to evolve.

An API is a contract between your server and the clients that use it. Once others depend on it, changing it is costly. Spending time on a clear design pays off for years.
1. Think in resources, not actions
A resource is a noun: users, orders, products. Use plural nouns in URLs and let the HTTP method be the verb.
| Action | Request |
|---|---|
| List orders | GET /orders |
| Get one order | GET /orders/42 |
| Create an order | POST /orders |
| Replace an order | PUT /orders/42 |
| Update part of an order | PATCH /orders/42 |
| Delete an order | DELETE /orders/42 |
| List items of an order | GET /orders/42/items |
Avoid verbs in URLs such as /getOrder or /createUser. For actions that do not fit CRUD, use a clear sub-resource or action path such as POST /orders/42/cancel.
Keep URLs lowercase, consistent and not too deeply nested.
2. Use methods correctly
- GET reads and must not change data. It is safe and idempotent.
- POST creates or triggers processing. Not idempotent by default.
- PUT replaces, PATCH partially updates, DELETE removes. PUT and DELETE are idempotent.
3. Return the right status codes
| Situation | Code |
|---|---|
| Success with a body | 200 OK |
| Created | 201 Created (with a Location header) |
| Success, no body | 204 No Content |
| Bad input | 400 Bad Request, or 422 for validation errors |
| Not logged in | 401 Unauthorized |
| Logged in but not allowed | 403 Forbidden |
| Not found | 404 Not Found |
| Conflict (e.g. duplicate) | 409 Conflict |
| Too many requests | 429 Too Many Requests (with Retry-After) |
| Server fault | 500, 502, 503 |
Never return 200 with an error hidden in the body.
4. Consistent error format
Give clients a predictable structure they can handle:
{
"error": {
"code": "validation_failed",
"message": "Email is not valid.",
"details": [{ "field": "email", "issue": "invalid_format" }],
"requestId": "a1b2c3"
}
}
Be helpful but do not leak internals: no stack traces, SQL or server paths. Log those on the server with the request ID.
5. Pagination, filtering and sorting
Never return unbounded lists.
- Pagination:
?limit=20&offset=40is simple. Cursor-based (?limit=20&after=abc) is more stable and faster for large or changing data. - Filtering:
?status=paid&created_after=2026-01-01. - Sorting:
?sort=-created_at. - Set a maximum page size.
- Return paging info (next cursor or total) in the response.
6. Version your API
Plan for change.
- Put the version in the URL (
/v1/orders) or a header. - Adding new optional fields is usually safe. Removing or renaming fields, or changing meaning, is a breaking change and needs a new version.
- Announce deprecations with dates, and give clients time to move.
7. Idempotency for safe retries
Networks fail, and clients retry. For POST requests with side effects (payments, orders), accept an Idempotency-Key header. If the same key arrives again, return the original result instead of creating a duplicate.
8. Security basics
- Use HTTPS only.
- Authenticate requests (tokens, API keys, OAuth) and authorise every request against the specific resource. A logged-in user must not read
/orders/43if it belongs to someone else (a very common bug, called BOLA or IDOR). - Validate all input: types, lengths, ranges, allowed values.
- Do not accept fields you did not intend to be set (mass assignment). Use explicit allow-lists.
- Rate limit and set request size limits.
- Do not put secrets in URLs, because they end up in logs.
- Return only the fields clients need. Do not leak other users' data or internal IDs unnecessarily.
- Configure CORS narrowly if browsers call the API.
9. Make it pleasant to use
- Consistent naming (pick
snake_caseorcamelCaseand keep it). - Use ISO 8601 for dates and UTC (
2026-11-07T10:00:00Z). - Use strings for IDs and for money use integers of the smallest unit (or decimal strings) to avoid floating-point errors.
- Support conditional requests and caching headers (
ETag,Cache-Control) for read-heavy data. - Compress responses.
10. Document and test
- Describe the API with OpenAPI and publish interactive docs.
- Include examples of requests, responses and errors.
- Write contract and integration tests.
- Provide a changelog.
Quick checklist
- Nouns in URLs, methods as verbs.
- Correct status codes, consistent errors.
- Pagination everywhere.
- Versioning and deprecation plan.
- Auth on every endpoint, ownership checks, validation.
- Idempotency for risky POSTs.
- OpenAPI docs and tests.
Takeaway: model resources, use methods and status codes properly, keep errors and naming consistent, paginate, version, secure every request and document with examples.
Programming roadmaps series, Part 2 (languages and design): 3 of 5. Previous: JavaScript and TypeScript roadmap: from first script to full stack.


