TL;DR
REST API URLs should identify nouns—resources—not verbs—actions. HTTP methods such as GET, POST, PUT, and DELETE provide the action. For example, use GET /pets?status=available, not GET /findPetsByStatus?status=available. Resource-oriented URLs create consistent patterns that are easier to use and maintain.
Introduction
Suppose you need an endpoint that returns pets by status. A descriptive first attempt might be:
GET /findPetsByStatus?status=available
It clearly describes the operation, but it models the endpoint like a function call. A REST-oriented design represents the resource instead:
GET /pets?status=available
The URL identifies the pets resource, while GET identifies the action.
The old Swagger Petstore used action-oriented paths such as:
GET /pet/findByStatus
GET /pet/findByTags
Modern PetstoreAPI replaces these with resource-oriented URLs. In this guide, you’ll learn how to:
- Identify verb-based URL designs
- Map CRUD operations to HTTP methods
- Use query parameters for filtering
- Model complex operations as sub-resources
- Test URL consistency with Apidog
- Migrate an existing API without breaking clients
The Verb Problem in REST APIs
Verbs in URLs usually indicate an RPC-style design rather than a REST-style design. The path reads like a function name instead of a resource identifier.
RPC-Style URLs
POST /createUser
GET /getUser?id=123
PUT /updateUser
DELETE /deleteUser?id=123
GET /findUsersByRole?role=admin
POST /sendEmail
GET /calculateTotal
These paths describe operations:
createUser()
getUser()
updateUser()
findUsersByRole()
sendEmail()
REST-Style URLs
POST /users
GET [REDACTED PATH]
PUT [REDACTED PATH]
DELETE [REDACTED PATH]
GET /users?role=admin
POST /emails
GET /orders/123/total
These paths identify resources. The HTTP method supplies the operation.
Why Resource-Oriented URLs Matter
Consistency
A resource-oriented API follows a predictable pattern:
| Method | Path | Operation |
|---|---|---|
GET |
/pets |
List pets |
POST |
/pets |
Create a pet |
GET |
/pets/{id} |
Get a pet |
PUT |
/pets/{id} |
Replace a pet |
DELETE |
/pets/{id} |
Delete a pet |
Once clients understand the /pets resource, they can predict the rest of the API.
With verb-based paths, every operation introduces a new naming pattern. Clients cannot infer whether the next endpoint will be /findPets, /getPet, /retrievePet, or something else.
Scalability
Verb-based APIs often add a new endpoint for every filter:
/findPetsByStatus
/findPetsByTags
/findPetsByOwner
/findPetsByBreed
/searchPets
/queryPets
A resource-oriented API can use one collection endpoint with query parameters:
GET /pets?status=available
GET /pets?tags=friendly
GET /pets?owner=john
GET /pets?breed=labrador
The resource remains the same while the query expresses the requested subset.
Use HTTP Methods as the Verbs
HTTP already defines the verbs your API needs. Use those semantics instead of encoding actions into paths.
HTTP Methods and CRUD
| HTTP method | Typical operation | Common semantics |
|---|---|---|
POST |
Create | Creates a new resource |
GET |
Read | Retrieves a resource without modifying it |
PUT |
Replace | Replaces the representation of a resource |
PATCH |
Partial update | Changes selected fields |
DELETE |
Delete | Removes a resource |
GET is safe and can be cached by browsers and proxies. PUT and DELETE are idempotent, so repeating the same request has the same intended result as sending it once. These semantics are useful when clients retry requests.
Example: User Management
Verb-Based Design
POST /createUser
GET /getUser?id=123
POST /updateUser
POST /deleteUser
Each operation uses a different path, and the HTTP method provides little useful information.
Resource-Oriented Design
POST /users
GET [REDACTED PATH]
PUT [REDACTED PATH]
DELETE [REDACTED PATH]
The resource path stays consistent. Only the method and resource identifier change.
Benefits
-
Caching —
GETrequests have standard caching semantics. UsingPOST /getUserprevents general-purpose caches from treating a read as a cacheable read. -
Idempotency —
PUTandDELETEprovide predictable retry behavior. -
Safety — A
GETrequest should not modify server state, so tools and crawlers can call it safely. - Interoperability — HTTP clients, proxies, gateways, and caches understand standard methods without custom conventions.
Examples from Swagger Petstore
The old Swagger Petstore includes several action-oriented endpoints.
Find Pets by Status
Old Swagger Petstore
GET /pet/findByStatus?status=available
Problems:
-
findByStatusis an action phrase - The path is inconsistent with
/pet/{id} - Adding more search criteria encourages more special-purpose endpoints
Modern PetstoreAPI
GET /pets?status=AVAILABLE
This design:
- Identifies the
petscollection - Uses a query parameter for filtering
- Matches the other pet endpoints
- Can be extended without renaming the path
For example:
GET /pets?status=AVAILABLE&species=dog
See the Modern PetstoreAPI REST documentation for the complete implementation.
Find Pets by Tags
Old Swagger Petstore
GET /pet/findByTags?tags=tag1,tag2
Modern PetstoreAPI
GET /pets?tags=friendly,trained
The collection remains /pets; tags describes the filter.
User Login
Old Swagger Petstore
GET /user/login?username=john&[REDACTED CREDENTIAL]
This design has several problems:
-
loginis an action in the path -
GETis inappropriate for an authentication operation - Credentials appear in the URL query string, where they may be exposed through logs, browser history, and monitoring systems
Modern PetstoreAPI
POST /auth/login
Content-Type: application/json
{
"username": "john",
"password": "secret123"
}
This design:
- Uses
POSTfor an operation that processes submitted credentials - Places credentials in the request body instead of the URL
- Uses an
/authresource namespace - Returns a JWT token for subsequent requests
How Modern PetstoreAPI Uses Resource-Oriented URLs
Modern PetstoreAPI applies the same naming pattern across its resources.
Pet Management
GET /pets
GET /pets?status=AVAILABLE
GET /pets?species=dog
GET /pets/{id}
POST /pets
PUT /pets/{id}
PATCH /pets/{id}
DELETE /pets/{id}
The paths contain resources and identifiers, not CRUD verbs.
Order Management
GET /orders
GET /orders/{id}
POST /orders
PUT /orders/{id}
DELETE /orders/{id}
GET /orders/{id}/items
/orders/{id}/items identifies the items sub-resource belonging to an order.
Complex Operations
Some operations do not map directly to basic CRUD. Model their result or related entity as a sub-resource:
POST /orders/{id}/payment
POST /orders/{id}/shipment
POST /pets/{id}/adoption
For example, /orders/{id}/payment represents the payment resource associated with an order. The path still describes a resource relationship, while POST indicates that the request creates or processes that resource.
When a Verb Seems Necessary
Not every operation is a simple create, read, update, or delete. Before adding a verb to a path, try modeling the operation as a collection, a filtered resource, or a related sub-resource.
Search Operations
Avoid an Action-Specific Path
GET /searchPets?query=labrador
Option 1: Query Parameters
GET /pets?search=labrador
Use this for straightforward filtering.
Option 2: A Search Resource
GET /pets/search?q=labrador
Use a dedicated search resource when search has a distinct representation or behavior.
Option 3: The QUERY Method
QUERY /pets
Content-Type: application/json
{
"query": "labrador",
"filters": {
"status": "AVAILABLE"
}
}
Use this pattern for complex search requests that do not fit comfortably in query parameters. Modern PetstoreAPI supports all three patterns depending on query complexity.
Calculations
Instead of exposing the calculation itself:
GET /calculateShipping?weight=10&destination=NY
Expose the result as a resource:
GET /shipping-estimates?weight=10&destination=NY
The client requests a shipping estimate; the URL does not need to describe the server’s internal calculation.
Batch Operations
Instead of:
POST /batchDeletePets
Use the collection and identify the resources to delete:
DELETE /pets?ids=1,2,3
For a more complex batch request, use a batch-operations resource:
POST /pets/batch-operations
Content-Type: application/json
{
"operation": "delete",
"ids": [1, 2, 3]
}
State Changes
Avoid separate action endpoints:
POST /activateUser
POST /deactivateUser
Treat the state as part of the user resource:
PATCH [REDACTED PATH]
Content-Type: application/json
{
"status": "ACTIVE"
}
The same pattern works for other state transitions when they are ordinary resource updates.
Test URL Design with Apidog
Apidog can help validate endpoint naming, test request behavior, and review an imported OpenAPI specification.
Import Modern PetstoreAPI
- Import the Modern PetstoreAPI OpenAPI specification.
- Import your own API specification.
- Review the generated endpoint structure.
- Compare collection paths, identifiers, filters, and related resources.
Check for Common Verbs
You can create a custom validation rule that flags common action words in paths:
const verbs = [
'get',
'create',
'update',
'delete',
'find',
'search',
'calculate',
'process',
'send',
'fetch'
];
const url = request.url.toLowerCase();
for (const verb of verbs) {
if (url.includes(`/${verb}`)) {
throw new Error(
`URL contains verb: ${verb}. Use a resource-oriented URL instead.`
);
}
}
Treat this as a review aid rather than an absolute rule. Some paths, such as /auth/login or /pets/search, may be intentional exceptions.
Test Endpoint Consistency
Verify that related operations use the same base resource:
GET /pets
POST /pets
GET /pets/{id}
PUT /pets/{id}
DELETE /pets/{id}
If an API instead uses /getPets, /createPet, and /removePet, clients have to learn unrelated paths for operations on the same resource.
Compare with Modern PetstoreAPI
Use Modern PetstoreAPI as a reference implementation:
- Import both specifications into Apidog
- Compare endpoint structures side by side
- Identify action words and inconsistent resource names
- Refactor paths while preserving request and response contracts
- Add tests for collection, item, filtering, and update operations
Migration Strategies
Changing URL patterns can break existing clients. Migrate incrementally.
Strategy 1: Version the API
Create a new API version with resource-oriented paths:
# Old API
GET /api/v1/findPetsByStatus?status=available
# New API
GET /api/v2/pets?status=available
Keep the old version available for backward compatibility and document the migration path.
Strategy 2: Add an Alias
Support both paths temporarily:
# Deprecated
GET /pet/findByStatus?status=available
# Preferred
GET /pets?status=available
Return a deprecation warning from the old endpoint:
{
"data": [],
"warnings": [
{
"code": "DEPRECATED_ENDPOINT",
"message": "This endpoint is deprecated. Use GET /pets?status=available instead.",
"sunset": "2027-01-01"
}
]
}
Monitor usage of the old path and communicate the removal date to clients.
Strategy 3: Redirect Simple GET Requests
For a simple GET migration:
GET /pet/findByStatus?status=available
Return:
301 Moved Permanently
Location: /pets?status=available
This approach works for GET requests, but redirects require more care for POST, PUT, and DELETE because clients and intermediaries may handle methods differently.
Conclusion
REST URLs should identify resources with nouns. HTTP methods provide the verbs. This separation produces APIs that are easier to understand, extend, test, and maintain.
Instead of:
GET /pet/findByStatus?status=available
Prefer:
GET /pets?status=AVAILABLE
Key takeaways:
- Use nouns in paths:
/pets,/orders,/users - Use HTTP methods for actions:
GET,POST,PUT,PATCH,DELETE - Use query parameters for filtering:
/pets?status=available - Model complex operations as sub-resources:
/orders/{id}/payment - Use request bodies for complex queries and submitted credentials
- Validate URL naming and endpoint consistency before publishing the API
- Migrate existing verb-based paths with versioning, aliases, or redirects
Check out the Modern PetstoreAPI documentation for complete examples of resource-oriented URL design.
FAQ
Can I ever use verbs in REST URLs?
Rarely. If an operation truly does not fit the resource model—such as some search or authentication flows—a verb may be acceptable. In most cases, first try modeling the operation as a resource, query, or sub-resource.
What about /login and /logout?
These are common exceptions. Many APIs use:
POST /auth/login
POST /auth/logout
Another option is to model authentication as sessions:
POST /sessions
DELETE /sessions/{id}
How should I handle complex queries?
Use query parameters for simple filters:
GET /pets?status=available&species=dog
For complex queries, use a search resource or the QUERY method:
POST /pets/search
What if an operation does not map to CRUD?
Model the result as a sub-resource. For example, instead of:
POST /processPayment
use:
POST /orders/{id}/payment
The payment is a resource related to the order.
How do I test whether URLs are resource-oriented?
Import your OpenAPI specification into Apidog and review the paths for action words, inconsistent collection names, and missing resource identifiers. Compare the structure with Modern PetstoreAPI.
Should I use /pets/search or /pets?search=query?
Both patterns can work. Use /pets?search=query for basic filtering. Use /pets/search or QUERY /pets when search has a distinct representation or requires complex parameters.
How do I migrate from verb-based URLs?
Use API versioning, temporary aliases, deprecation warnings, and a documented sunset date. Redirect simple GET requests where appropriate. See the migration strategies above for examples.
Does Modern PetstoreAPI use verbs in URLs?
Modern PetstoreAPI avoids verbs in URLs. Filtering and search use query parameters or search resources, while authentication and related operations are modeled as resources or sub-resources. Consult the REST API documentation for complete examples.
Top comments (0)