An API is an interface for other developers, and like any interface it can be intuitive or frustrating. A well-designed API can be understood by reading a few examples, behaves predictably and fails clearly. There is no single correct form, but there are established conventions that make life easier for those who use it and those who maintain it.
Thinking in resources
In the REST style, URLs identify resources (things), while HTTP methods indicate actions. URLs therefore contain nouns, usually plural, not verbs:
GET /articles list of articles
POST /articles create an article
GET /articles/42 details of article 42
PATCH /articles/42 partial update
DELETE /articles/42 deletion
GET /articles/42/comments comments on article 42
Avoid forms such as /getArticles or /articles/42/delete: they duplicate in the URL information the method already carries.
HTTP methods and what they mean
| Method | Use | Safe | Idempotent |
|---|---|---|---|
GET |
read | yes | yes |
POST |
create or start an operation | no | no |
PUT |
replace entirely | no | yes |
PATCH |
modify partially | no | not necessarily |
DELETE |
delete | no | yes |
- Safe means it does not change server state. A
GETmust never delete or modify data: crawlers, link previews and browser prefetching all issue GET requests automatically. - Idempotent means repeating the request several times has the same effect as making it once. This matters because clients tend to retry requests after a network error.
For POST operations that must not be duplicated, such as a payment or an order, a widespread approach is to accept an idempotency key sent by the client in a header: if the same key arrives twice, the server returns the result of the first request instead of executing it again.
Status codes that tell the truth
The status code is the first thing the client checks. Always returning 200 with an "error": true field in the body forces every client to interpret the content and breaks tools, caches and monitoring.
The most useful codes:
200 OK: request succeeded, with content.201 Created: resource created; include theLocationheader with the URL of the new resource.204 No Content: succeeded, with no content (typical of DELETE).400 Bad Request: malformed request.401 Unauthorized: authentication missing or invalid.403 Forbidden: authenticated, but without permission.404 Not Found: the resource does not exist.409 Conflict: conflict with the current state, such as a duplicate.422 Unprocessable Content: correct syntax but invalid data.429 Too Many Requests: rate limit exceeded.500 Internal Server Error: unexpected server error.
Consistent, useful errors
Every error should have the same structure, so the client can handle them in one place. There is also an IETF standard for this purpose, "Problem Details", with the content type application/problem+json. An example:
{
"type": "https://api.example.com/errors/validation",
"title": "Invalid data",
"status": 422,
"errors": [
{ "field": "email", "message": "Invalid format" }
]
}
Two rules: the message must help whoever integrates the API understand what to fix, and it must not reveal internal details such as SQL queries, file paths or stack traces.
Lists: pagination, filtering and sorting
A list should never return every item at once. The two main pagination techniques:
- Offset and limit:
GET /articles?limit=20&offset=40. Simple, and it lets you jump to any page, but it gets slow on huge tables and can skip or duplicate items if the data changes between requests. - Cursor:
GET /articles?limit=20&after=eyJpZCI6MTIzfQ. The server returns an opaque cursor for the next page. It is stable and efficient, but it does not let you jump straight to page 37.
{
"data": [ ... ],
"next_page": "/articles?limit=20&after=eyJpZCI6MTQzfQ"
}
Filtering and sorting are expressed with readable query parameters: ?status=published&sort=-date. Always set a server-side maximum for limit, to prevent requests asking for a million rows.
Versioning
Sooner or later a change will break compatibility. Common strategies are putting the version in the URL (/v1/articles) or in a header. The URL version is the easiest to understand and try out. Whatever you choose:
- adding new fields to responses usually breaks nothing;
- removing or renaming fields, or changing their type, requires a new version;
- announce deprecations well in advance and keep the previous version running for a reasonable period.
Basic security
- HTTPS only, always.
- Authentication with tokens sent in the
Authorizationheader, never in URL parameters, which end up in logs. - Authorisation checks on every resource: checking that the user is authenticated is not enough; you must check that article 42 belongs to them. It is one of the most common API mistakes.
- Strict input validation and rate limiting.
- Responses that include only the necessary fields: do not return the entire database record "for convenience".
Documentation
An API without documentation is an API nobody knows how to use. An OpenAPI specification describes endpoints, parameters and responses in a format readable by people and tools, and makes it possible to generate interactive documentation and clients.
In short
- Use resource-based URLs and let HTTP methods indicate the action.
- Respect the safety and idempotency of methods; GET must never modify data.
- Return truthful status codes and errors with a consistent structure and no internal details.
- Always paginate lists and set a server-side maximum.
- Check authorisation on every single resource and document the API with examples.