Quick Answer

Name resources with plural nouns, use HTTP methods for the verb, return meaningful status codes, paginate every list, and keep error responses in one consistent shape. Consistency matters more than any individual choice.

URLs name things, methods do things

The single most common mistake is putting verbs in URLs:

POST /getStudent          bad
POST /createStudent       bad
POST /deleteStudent/5     bad

The HTTP method is already the verb. The URL should name the resource:

GET    /students          list
POST   /students          create
GET    /students/5        fetch one
PUT    /students/5        replace
PATCH  /students/5        partial update
DELETE /students/5        delete

Use plural nouns consistently. Mixing /student/5 and /students means every developer must remember which endpoint uses which.

Nest to express relationships, but only one level: /students/5/enrolments is clear, while /students/5/enrolments/9/assignments/3/comments is not. Beyond one level, expose the resource at the top with a filter instead.

Method semantics matter

These are not arbitrary labels — clients, proxies and caches rely on them.

  • GET must not change anything. A GET that deletes will eventually be triggered by a crawler or a prefetch.
  • GET, PUT and DELETE are idempotent — repeating them gives the same end state. POST is not, which is why a duplicate form submission creates two records.
  • PUT replaces the whole resource; PATCH updates part of it. Sending a partial body to PUT should logically blank the missing fields, which is why PATCH exists.

Idempotency matters for retries. A client that times out on a POST cannot safely retry, since it does not know whether the first attempt succeeded. Support an idempotency key header for such endpoints — see idempotency explained.

Status codes are part of the contract

Returning 200 OK with {"success": false} forces every client to parse the body to discover a failure, and breaks every tool that reasons about HTTP.

  • 200 OK · 201 Created, with a Location header · 204 No Content, typical for DELETE
  • 400 malformed or invalid · 401 not authenticated · 403 authenticated but not permitted · 404 not found
  • 409 conflict, such as a duplicate · 422 well-formed but semantically invalid · 429 rate limited
  • 500 server error · 503 temporarily unavailable

The 4xx versus 5xx split carries real meaning: 4xx means the client must change something, 5xx means retrying may work. Monitoring and retry logic both depend on that distinction being honest, so do not return 500 for validation failures.

Pagination, filtering and sorting

Paginate every list endpoint from the start. An endpoint returning all records works fine with 50 rows in development and falls over at 500,000 in production. Retrofitting pagination is a breaking change.

GET /students?page=2&limit=50
GET /students?cursor=eyJpZCI6MTAwfQ&limit=50

Offset pagination is simple but slows on deep pages and can skip or repeat rows when data changes mid-traversal. Cursor pagination is stable and efficient, and is the better default for large or frequently-changing datasets.

Return the metadata clients need — total count where feasible, and a next-page cursor or link.

Use query parameters for filtering and sorting, and keep them consistent: ?stream=Science&min_marks=80&sort=-marks, where the leading minus means descending. Whatever convention you choose, apply it everywhere.

One error shape, everywhere

Clients write error handling once. If every endpoint returns a differently-shaped error, they cannot.

{
  "error": {
    "code": "validation_failed",
    "message": "marks must be between 0 and 100",
    "details": [{ "field": "marks", "issue": "out_of_range" }]
  }
}

A stable machine-readable code matters more than the message, because clients should branch on the code and display the message. Changing wording then never breaks anyone.

Never leak stack traces, SQL or file paths in production errors — they tell an attacker about your schema and structure.

Two closing points. Version from day one (/v1/students) so you can change later without breaking clients — see API versioning strategies. And write the documentation as you build; an API nobody can use without reading your source code is not finished.

Frequently Asked Questions

Should API URLs use singular or plural nouns? Plural, consistently — /students and /students/5. The important part is consistency, so developers never have to remember which endpoints differ.
What is the difference between PUT and PATCH? PUT replaces the entire resource, so omitted fields should logically be cleared. PATCH applies a partial update, changing only the fields provided.
Why not return 200 with an error message in the body? It forces every client to parse the body to detect failure and breaks tools, proxies and monitoring that reason about status codes. The status code is part of the contract.
Offset or cursor pagination? Offset is simpler and fine for small datasets. Cursor pagination is more efficient on deep pages and does not skip or duplicate rows when data changes during traversal.
Do I need to version my API? Yes, from the beginning. Adding a version later is itself a breaking change, and a version prefix costs nothing when nobody is using it yet.