You've seen URLs like these:
GET /findUsersByEmail?email=user@example.com
POST /createOrder
GET /searchProducts?query=laptop
DELETE /removeItem?id=123
They look reasonable. They describe what the endpoint does. But they violate a fundamental REST principle: URLs should represent resources (nouns), not actions (verbs).
If your API uses action verbs in URLs, you're not building a REST API. You're building an RPC API with HTTP as the transport layer.
Here's why that matters and how to fix it.
Why Action Verbs Break REST
REST (Representational State Transfer) is built on resources. A resource is a thing—a user, an order, a product. Resources have representations (JSON, XML) and you transfer state by performing operations on them.
The HTTP method tells you the action: - GET = retrieve - POST = create - PUT = replace - PATCH = update - DELETE = remove
When you put action verbs in URLs, you're duplicating information that HTTP already provides. Worse, you're creating inconsistency.
Example: The Old Swagger Petstore
The classic Swagger Petstore includes these endpoints:
GET /pet/findByStatus?status=available
GET /pet/findByTags?tags=tag1,tag2
Both endpoints retrieve pets. The HTTP method (GET) already says "find" or "retrieve." Adding "findBy" to the URL is redundant.
This creates problems:
Inconsistency: Why is it /pet/findByStatus but /pet/{id} (not /pet/getById)? The pattern breaks down.
Scalability: What happens when you need to find by breed? Add /pet/findByBreed? Soon you have dozens of "find" endpoints.
Confusion: Is /pet/findByStatus different from /pet?status=available? They do the same thing but look different.
The Resource-Oriented Approach
REST APIs should be resource-oriented. URLs identify resources, HTTP methods specify actions.
Replace Action Verbs with Query Parameters
Instead of encoding the action in the URL, use query parameters:
Bad:
GET /findUsersByEmail?email=user@example.com
GET /findUsersByRole?role=admin
GET /findUsersByStatus?status=active
Good:
GET /users?email=user@example.com
GET /users?role=admin
GET /users?status=active
The URL identifies the resource (/users). Query parameters filter the collection. The HTTP method (GET) specifies the action (retrieve).
This approach scales. Need to filter by multiple criteria? Combine parameters:
GET /users?role=admin&status=active&created_after=2024-01-01
No new endpoints needed. The pattern is consistent and predictable.
Use HTTP Methods for Actions
HTTP methods provide all the verbs you need:
Bad:
POST /createOrder
POST /updateOrder
POST /deleteOrder
Good:
POST /orders ← Create
PUT /orders/{id} ← Replace
PATCH /orders/{id} ← Update
DELETE /orders/{id} ← Remove
The URL identifies the resource. The method specifies the action. No redundancy.
Handle Complex Queries with the QUERY Method
Sometimes query parameters aren't enough. You need complex filters, sorting, pagination, and field selection. Encoding all this in the URL creates unwieldy strings.
For complex queries, use the QUERY method (RFC 9535):
QUERY /pets/search
Content-Type: application/json
{
"filters": {
"status": "AVAILABLE",
"breed": "Golden Retriever",
"age": {"min": 1, "max": 5},
"vaccinated": true
},
"sort": [
{"field": "age", "order": "asc"}
],
"pagination": {
"limit": 20,
"cursor": "eyJpZCI6IjAxOWI0MTMyIn0"
}
}
The QUERY method is semantically correct for searches. It allows a request body (unlike GET) and doesn't modify state (unlike POST).
The URL still identifies the resource (/pets). The method (QUERY) specifies the action (search). The request body contains the search criteria.
Common Patterns and How to Fix Them
Pattern 1: Create Actions
Bad:
POST /createUser
POST /addProduct
POST /registerCustomer
Good:
POST /users
POST /products
POST /customers
The POST method already means "create." Don't repeat it in the URL.
Pattern 2: Update Actions
Bad:
POST /updateUser?id=123
POST /modifyProduct?id=456
Good:
PUT /users/123 ← Full replacement
PATCH /users/123 ← Partial update
Use PUT for full replacement, PATCH for partial updates. The method conveys the action.
Pattern 3: Delete Actions
Bad:
POST /deleteUser?id=123
GET /removeProduct?id=456
Good:
DELETE /users/123
DELETE /products/456
The DELETE method is explicit. Don't use GET or POST for deletions.
Pattern 4: Search Actions
Bad:
GET /searchProducts?query=laptop
POST /findUsers
GET /lookupOrders?id=123
Good:
GET /products?q=laptop ← Simple search
QUERY /products/search ← Complex search
GET /orders/123 ← Lookup by ID
Use query parameters for simple searches, QUERY method for complex searches, and direct resource access for lookups.
Pattern 5: Bulk Actions
Bad:
POST /deleteMultipleUsers
POST /updateManyProducts
Good:
DELETE /users?ids=123,456,789
PATCH /products?ids=abc,def,ghi
Or use a batch endpoint:
POST /batch
Content-Type: application/json
{
"operations": [
{"method": "DELETE", "path": "/users/123"},
{"method": "DELETE", "path": "/users/456"}
]
}
The batch approach is cleaner for complex multi-resource operations.
Pattern 6: State Transitions
Bad:
POST /approveOrder?id=123
POST /cancelSubscription?id=456
POST /activateUser?id=789
Good:
PATCH /orders/123
Content-Type: application/json
{"status": "APPROVED"}
State transitions are updates. Use PATCH with the new state.
For complex workflows, consider a sub-resource:
POST /orders/123/approval
POST /subscriptions/456/cancellation
POST /users/789/activation
This makes the action explicit while keeping the URL resource-oriented.
When Action Verbs Are Acceptable
There are rare cases where action verbs make sense:
1. Non-CRUD Operations
Some operations don't map to CRUD:
POST /orders/123/refund
POST /users/456/password-reset
POST /documents/789/convert
These are actions that don't fit the resource model. They're acceptable because they represent operations, not resources.
2. RPC-Style Endpoints
If you're building an RPC API (not REST), action verbs are fine:
POST /rpc/calculateShipping
POST /rpc/validateAddress
POST /rpc/generateReport
But be honest about what you're building. Don't call it REST if it's RPC.
3. Controller Resources
Some APIs use "controller" resources for actions:
POST /payments/123/capture
POST /emails/456/send
POST /jobs/789/retry
This is a middle ground. The URL is still resource-oriented (/payments/123) but includes an action sub-resource (/capture).
Real-World Examples
GitHub API
GitHub uses resource-oriented URLs:
Good:
GET /repos/{owner}/{repo}
POST /repos/{owner}/{repo}/issues
PATCH /repos/{owner}/{repo}/issues/{number}
Not:
GET /getRepository
POST /createIssue
POST /updateIssue
Stripe API
Stripe follows REST principles:
Good:
GET /customers
POST /customers
GET /customers/{id}
DELETE /customers/{id}
Not:
POST /createCustomer
POST /deleteCustomer
Modern PetStore API
The Modern PetStore API fixes the old Petstore's mistakes:
Old Petstore (Bad):
GET /pet/findByStatus?status=available
GET /pet/findByTags?tags=tag1,tag2
Modern PetStore (Good):
GET /pets?status=AVAILABLE
GET /pets?tags=tag1,tag2
QUERY /pets/search
How to Refactor Your API
If your API uses action verbs, here's how to refactor:
Step 1: Identify Resources
List all your endpoints and identify the underlying resources:
/createUser → resource: users
/findProducts → resource: products
/updateOrder → resource: orders
Step 2: Map Actions to HTTP Methods
Match each action to the appropriate HTTP method:
createUser → POST /users
findProducts → GET /products
updateOrder → PATCH /orders/{id}
Step 3: Use Query Parameters for Filters
Replace action-based endpoints with filtered resource endpoints:
/findProductsByCategory → GET /products?category=electronics
/searchUsersByEmail → GET /users?email=user@example.com
Step 4: Version Your API
If you're refactoring an existing API, version it:
Old: GET /v1/findProducts
New: GET /v2/products
This lets you migrate clients gradually without breaking existing integrations.
Step 5: Document the Changes
Provide clear migration guides:
## Migration Guide: v1 to v2
### Finding Products
**v1 (Deprecated)**:
GET /v1/findProducts?category=electronics
**v2**:
GET /v2/products?category=electronics
Testing Your API Design
Use these questions to evaluate your URLs:
- Does the URL identify a resource? If not, refactor.
- Does the HTTP method specify the action? If not, you're duplicating information.
- Can you explain the endpoint without using verbs? If not, it's probably not resource-oriented.
- Is the pattern consistent across all endpoints? If not, simplify.
Conclusion
Action verbs in URLs are a code smell. They indicate you're building RPC, not REST.
REST APIs should be resource-oriented: - URLs identify resources (nouns) - HTTP methods specify actions (verbs) - Query parameters filter collections - The QUERY method handles complex searches
This approach creates consistent, scalable, predictable APIs that developers love.
The Modern PetStore API demonstrates these principles in action. Every endpoint follows the resource-oriented pattern. No action verbs. No inconsistency. Just clean, RESTful design.
Want to see it in practice? Check out the Modern PetStore API documentation at docs.petstoreapi.com.
from Anakin Blog http://anakin.ai/blog/stop-using-action-verbs-in-urls/
via IFTTT



