
An API is the contract between your frontend and backend. When that contract is well-designed, development is smooth, bugs are rare, and the frontend team can work independently from the backend team. When the API is poorly designed, every feature becomes a coordination problem, error handling is inconsistent, and performance issues are hard to diagnose.
Whether you are designing an API manually or reviewing one generated by AI, understanding established patterns saves time and prevents common mistakes.
REST APIs organize around resources -- nouns, not verbs. A resource is a concept your application manages: users, projects, tasks, orders. Each resource gets a predictable set of endpoints:
GET /api/users -- List all users (with pagination)GET /api/users/:id -- Get a single userPOST /api/users -- Create a new userPUT /api/users/:id -- Update an existing userDELETE /api/users/:id -- Delete a userThis convention means anyone familiar with REST can immediately understand your API without reading documentation. Nested resources express relationships: GET /api/projects/:id/tasks returns all tasks belonging to a project.
Using the correct HTTP status code is one of the simplest ways to make an API developer-friendly. The most important codes to use correctly:
APIs that return 200 for everything and put the real status in the response body are harder to work with because generic error handling cannot distinguish success from failure.
Any endpoint that returns a list of resources needs pagination. Without it, a table with 100,000 records will return all of them in a single response, crushing both the server and the client. The two most common pagination strategies are offset-based and cursor-based.
Offset-based pagination uses ?page=2&limit=20 parameters. It is simple to implement and understand, and works well for datasets that do not change frequently. The downside is that inserting or deleting records can cause items to shift between pages.
Cursor-based pagination uses a cursor (typically the ID of the last item) to fetch the next batch: ?after=abc123&limit=20. It is more reliable for real-time data because it is not affected by insertions or deletions, but it does not support jumping to a specific page number.
Consistent error responses make debugging faster. A good error response includes: an error code (machine-readable), a message (human-readable), and optionally the specific field that caused the error. For example:
{
"error": "VALIDATION_ERROR",
"message": "Email address is invalid",
"field": "email"
}
For endpoints that validate multiple fields, return all errors at once rather than one at a time. This saves the client from submitting the form repeatedly to discover each error.
JWT (JSON Web Tokens) is the most common authentication pattern for modern web APIs. The client sends credentials (email and password), receives a token, and includes that token in the Authorization header of subsequent requests. The server validates the token without needing to query a session store.
For applications that need revocation (logging out a user immediately), combine JWTs with a short expiration time and a refresh token stored in an HTTP-only cookie. The access token expires quickly (15-60 minutes), and the refresh token is used to get a new access token without requiring the user to log in again.

/api/v1/users) to allow future changesGood API design is an investment that pays dividends throughout a project's lifecycle. It makes frontend development faster, debugging easier, and third-party integrations smoother. Whether you are designing an API from scratch or working with one generated by AI tools, these patterns provide a reliable foundation.
The best APIs are invisible -- they work exactly as you expect, every time.
Published by João Castro · 3 min read