API Design · Backend Architecture
REST API Design: How to Structure Endpoints Properly
A messy API is not just ugly. It forces every developer who touches it to waste hours guessing what an endpoint does, how to call it, and why it is breaking. This guide covers the exact principles behind clean, predictable endpoint structure.
A few months ago, a startup building software for urban beekeepers asked me to review their API. They had twelve developers, a beautiful frontend, and a working product. But when I opened their API documentation, I found endpoints like /getHiveData, /apiary-info-update, and /deleteQueenById?id=4. Three different developers had built three different sections of the API, and none of them had agreed on a naming convention. The frontend team was constantly asking the backend team what parameters an endpoint expected. Integrations took weeks instead of days.
Their code worked. The API functioned. But it was structured so poorly that it was actively slowing the entire company down. They did not have a technical problem. They had a design problem. REST API design is not about making things work. It is about making things predictable, scalable, and easy for other humans to understand.
REST stands for Representational State Transfer. It is not a protocol or a standard. It is an architectural style, a set of constraints that, if you follow them, make your API intuitive. The most important constraint is that your API should treat everything as a resource, and those resources should be addressed using standard HTTP methods.
In our beekeeping system, a hive is a resource. An apiary is a resource. A queen bee is a resource. A honey harvest is a resource. Your job as an API designer is to give each resource a clear, consistent address on the web, and let the HTTP methods do the heavy lifting of describing what action is being taken.
- Use nouns, not verbs: The URL identifies the resource. The HTTP method identifies the action.
- Always use plural nouns: Use
/hivesinstead of/hiveto represent collections consistently. - Nest resources logically: Use
/apiaries/1/hivesto show ownership, but never nest deeper than one level. - Use query parameters for filtering: Append
?status=active&sort=-createdAtto modify the collection view. - Version your API from day one: Prefix your base URL with
/v1/to allow future breaking changes without breaking existing clients.
- Backend developers designing their first or second API and want to get the structure right from the start.
- Frontend developers who consume APIs and want to understand what good design looks like so they can advocate for it.
- Technical leads who need a reference to enforce consistent API conventions across their team.
- Anyone transitioning from RPC-style endpoints to RESTful design.
Rule 1: Use Nouns, Not Verbs
This is the single most common mistake beginners make. They put the action into the URL. They build endpoints like /getHives, /createApiary, or /updateQueen. This completely misses the point of HTTP.
HTTP already has verbs. They are the methods: GET, POST, PUT, PATCH, DELETE. If you put a verb in the URL, you are duplicating information and creating a rigid structure that is hard to extend.
| Wrong (Verbs in URL) | Right (Nouns + HTTP Methods) |
|---|---|
GET /getHives |
GET /hives |
POST /createApiary |
POST /apiaries |
PUT /updateQueen/4 |
PUT /queens/4 |
DELETE /removeHive/12 |
DELETE /hives/12 |
When you see DELETE /hives/12, you immediately know what is happening. The method says delete. The URL says which hive. It is clean, it is standard, and any developer familiar with REST can use your API without reading a single page of documentation.
The exception: Some actions are not simple CRUD operations. Things like POST /hives/12/activate or POST /queens/4/mark-missing are acceptable because they represent domain-specific state changes that do not map neatly to PUT or PATCH. Use these sparingly.
Rule 2: Always Use Plural Nouns
Should your endpoint be /hive or /hives? Always use the plural form. A URL represents a collection of resources. Even if you are acting on a single item, the base path remains plural because you are reaching into the collection to pull out a specific member.
GET /hives returns the whole collection. GET /hives/12 returns hive number 12 from that collection. POST /hives adds a new member to the collection. Keeping everything plural means you never have to remember whether a specific resource was singular or plural. The rule is absolute and unvarying.
Do not mix singular and plural. If you have /hives and /queen in the same API, developers will constantly guess which one you used. Pick plural and apply it to every single resource without exception.
Rule 3: Nest Resources to Show Relationships
Resources do not exist in isolation. A hive belongs to an apiary. An inspection belongs to a hive. A harvest record belongs to a hive. Nesting in the URL expresses this ownership.
If you want to see all the hives in apiary number 3, the endpoint is GET /apiaries/3/hives. This is highly readable. It tells you exactly what you are getting: the hives that belong to apiary 3.
The one-level deep rule: Never nest deeper than one level. /apiaries/3/hives is good. /apiaries/3/hives/5/inspections/8 is terrible. Deep nesting makes URLs fragile, hard to cache, and annoying to type. If you need inspection 8, just use GET /inspections/8.
Rule 4: Use Query Parameters for Filtering, Sorting, and Pagination
The path segment of your URL should only contain resource identifiers. Everything else, like filters, sorting rules, and pagination controls, belongs in the query string after the question mark.
If you want active hives in apiary 3, sorted by creation date, you do not put that in the path. You use: GET /apiaries/3/hives?status=active&sort=-createdAt. The path stays clean. The modifiers are clearly separated.
For pagination, the standard approach is to use page and limit: GET /hives?page=2&limit=20. Always include pagination metadata in your response body so the client knows how many total results exist and how many pages are left.
Cursor-based pagination: For large datasets, consider cursor-based pagination using ?cursor=abc123 instead of page numbers. It performs better on large tables because it does not require an OFFSET query. Return the next cursor in your response metadata.
Rule 5: Version Your API from Day One
Someday, you will need to make a change that breaks backward compatibility. Maybe you rename a field from "weightKg" to "weightGrams." If you do not have versions, making that change breaks every single app that uses your API. Versioning gives you a clean escape hatch.
The most common and practical approach is URL versioning: /v1/hives. When you need to make breaking changes, you deploy /v2/hives and keep /v1 running for legacy clients. It is simple, visible in the URL, and extremely easy to test.
| Approach | Example | Pros | Cons |
|---|---|---|---|
| URL | /v1/hives |
Visible, easy to test | Slightly longer URLs |
| Header | Accept: application/vnd.api.v1+json |
Clean URLs | Hard to test in browser |
| Query | /hives?version=1 |
Easy to add | Easy to forget |
Rule 6: Use HTTP Status Codes Correctly
Your endpoint structure is only half the design. The other half is how you respond. Do not return a 200 OK with an error message hidden inside the JSON body. Use the proper status codes. Return 201 Created when a POST succeeds. Return 204 No Content when a DELETE succeeds. Return 400 Bad Request when the client sends invalid data. The status code is the first thing a developer looks at. Make it truthful.
The silent killer: Returning 200 OK with {"error": "Insufficient permissions"} inside the body. This forces every client to parse the response body to figure out if the request actually succeeded. Status codes exist precisely to prevent this. Use 403 Forbidden instead.
Rule 7: Use Consistent Response Structures
Every response, whether it is a single resource or a collection, should follow the same structural pattern. For collections, always wrap the data in an object that includes the array of items plus pagination metadata. For errors, always return the same error object shape.
A good collection response looks like this: the root object has a data array containing the items, a meta object with pagination info like total count and current page, and optionally a links object with URLs for the next and previous pages. When every endpoint follows this pattern, developers can write generic client code that handles any endpoint without customization.
Putting It All Together: The BeeKeeper API Map
When you follow these principles, your entire API becomes predictable. Here is what the endpoint map for our beekeeping system looks like when designed properly:
GET /v1/apiaries— List all apiariesPOST /v1/apiaries— Create an apiaryGET /v1/apiaries/3— Get apiary 3PATCH /v1/apiaries/3— Update apiary 3DELETE /v1/apiaries/3— Delete apiary 3GET /v1/apiaries/3/hives— List hives in apiary 3POST /v1/apiaries/3/hives— Add a hive to apiary 3GET /v1/hives/12— Get hive 12 directlyGET /v1/hives/12/inspections— List inspections for hive 12POST /v1/hives/12/inspections— Log a new inspectionGET /v1/harvests?hiveId=12&year=2026— Filter harvests by hive and year
Notice the pattern. Every collection is plural. Every specific item is an ID appended to the collection. Nesting only goes one level deep. Query parameters handle filtering. If a developer knows how to interact with apiaries, they instantly know how to interact with hives, queens, and harvests. That consistency is the entire point of REST.
Ready to redesign your API?
Start by listing every endpoint you currently have. Highlight the ones with verbs in the URL, the ones that are singular, and the ones that nest deeper than one level. That list is your refactoring roadmap.
You do not have to change everything at once. Fix one resource at a time, keep the old endpoints working with redirects, and deprecate them in the next version.
Frequently Asked Questions
No. REST endpoints should use nouns that represent resources, like /users. The verb is provided by the HTTP method itself. GET /users retrieves them, POST /users creates one, and DELETE /users/5 removes one. Putting verbs in the URL defeats the purpose of using HTTP methods.
A best practice is to never nest more than one level deep. /apiaries/1/hives is good. /apiaries/1/hives/3/inspections/8 is too deep. For anything deeper, flatten the structure by putting the identifier in a query parameter instead, like /inspections/8?hiveId=3.
Always use plural nouns. Use /hives instead of /hive. A URL represents a collection of resources. Even if you are acting on a single item, like /hives/5, the base path remains plural because you are pulling a specific item out of the plural collection.
Both approaches are valid, but URL versioning like /v1/hives is more common, easier for beginners, and simpler to test visually. Header versioning keeps the URL perfectly clean but makes casual browser exploration impossible. For most teams, URL versioning is the pragmatic choice.
Authentication should be handled via HTTP headers, typically an Authorization header with a Bearer token, never in the URL. Pagination should be handled via query parameters, such as /hives?page=2&limit=20. The response should include metadata telling you the total number of items and total pages.
Need Help?
Share your API design for feedback or ask a question about REST conventions.
