Friday, March 13, 2026

Stop Using Action Verbs in REST API URLs

Stop Using Action Verbs in REST API URLs

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:

  1. Does the URL identify a resource? If not, refactor.
  2. Does the HTTP method specify the action? If not, you're duplicating information.
  3. Can you explain the endpoint without using verbs? If not, it's probably not resource-oriented.
  4. 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

No comments:

Post a Comment

Stop Using Action Verbs in REST API URLs

You've seen URLs like these: GET /findUsersByEmail?email=user@example.com POST /createOrder GET /searchProducts?query=laptop DELETE /r...