Every example below follows the conventions in our rulebook: plural nouns, kebab-case, no verbs, versioned. Generate your own set in the endpoint machine — these are here to show the patterns.
Users — the classic CRUD set
| Method | Endpoint | Does what |
|---|---|---|
| GET | /api/v1/users | List users (paginated) |
| POST | /api/v1/users | Create a user |
| GET | /api/v1/users/{userId} | Get one user |
| PUT | /api/v1/users/{userId} | Replace a user |
| PATCH | /api/v1/users/{userId} | Update fields |
| DELETE | /api/v1/users/{userId} | Delete a user |
Note the shape: one path, six methods. The collection and the item share everything except the identifier — this is the pattern to internalize.
Orders — nested under customers
| Method | Endpoint | Does what |
|---|---|---|
| GET | /api/v1/customers/{customerId}/orders | List a customer's orders |
| POST | /api/v1/customers/{customerId}/orders | Create an order for a customer |
| GET | /api/v1/customers/{customerId}/orders/{orderId} | Get one order |
One level of nesting expresses ownership clearly. If orders also need a global view, add a top-level /api/v1/orders collection with a ?customer= filter rather than nesting deeper.
Products — multi-word resources in kebab-case
| Method | Endpoint | Does what |
|---|---|---|
| GET | /api/v1/product-categories | List categories |
| GET | /api/v1/product-categories/{categoryId}/products | Products in a category |
| POST | /api/v1/products | Create a product |
Multi-word resources get hyphens: product-categories, never productCategories or product_categories in the path.
Query patterns — filter, sort, paginate
| Pattern | Example |
|---|---|
| Filter | /api/v1/users?status=active |
| Sort | /api/v1/users?sort=created&order=desc |
| Paginate | /api/v1/users?page=2&pageSize=20 |
| Combined | /api/v1/orders?status=shipped&sort=total&page=1&pageSize=50 |
What bad looks like
| Bad | Good | Why |
|---|---|---|
| /getUsers | GET /users | Verb in path |
| /user | /users | Singular collection |
| /UserProfiles | /user-profiles | Case + no kebab |
| /users.json | /users | File extension |
| /users/ | /users | Trailing slash |
| /api/users/list/active | /api/v1/users?status=active | Filter as path + unversioned |
Auth and session endpoints
| Method | Endpoint | Does what |
|---|---|---|
| POST | /api/v1/auth/login | Exchange credentials for tokens |
| POST | /api/v1/auth/refresh | Refresh an access token |
| POST | /api/v1/auth/logout | Invalidate the session |
| GET | /api/v1/me | Current user profile |
Auth is the exception that proves the nouns rule: login and refresh read like verbs because they are actions, modeled as sub-resources under an auth collection. The alternative — POST /login at the root — works too, but namespacing under /auth keeps session machinery visibly grouped.
Mistakes seen in the wild
Real APIs ship /api/getAllUsers, /api/user next to /api/orders, and /api/v1/users/list?active=true more often than anyone admits. Each one seemed harmless in isolation; together they make an API nobody can guess. The fix is never a big rewrite — it is a naming review on every new endpoint, with the eight conventions as the checklist. Generate the set, review the set, then freeze it. Teams that skip this step do not notice the cost until the API has fifty endpoints and three competing styles — by then, every fix is a breaking change. Convention debt compounds exactly like technical debt — except it is visible to every developer who ever reads your docs.
Steal these patterns freely. The best API naming decision is the one your whole team can apply without thinking.