DEV Community

Preecha
Preecha

Posted on

Why Shouldn't You Use Verbs in REST API URLs?

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.

Try Apidog today

Introduction

Suppose you need an endpoint that returns pets by status. A descriptive first attempt might be:

GET /findPetsByStatus?status=available
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

These paths describe operations:

createUser()
getUser()
updateUser()
findUsersByRole()
sendEmail()
Enter fullscreen mode Exit fullscreen mode

REST-Style URLs

POST /users
GET [REDACTED PATH]
PUT [REDACTED PATH]
DELETE [REDACTED PATH]
GET /users?role=admin
POST /emails
GET /orders/123/total
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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]
Enter fullscreen mode Exit fullscreen mode

The resource path stays consistent. Only the method and resource identifier change.

Benefits

  1. CachingGET requests have standard caching semantics. Using POST /getUser prevents general-purpose caches from treating a read as a cacheable read.
  2. IdempotencyPUT and DELETE provide predictable retry behavior.
  3. Safety — A GET request should not modify server state, so tools and crawlers can call it safely.
  4. 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
Enter fullscreen mode Exit fullscreen mode

Problems:

  • findByStatus is 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
Enter fullscreen mode Exit fullscreen mode

This design:

  • Identifies the pets collection
  • 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
Enter fullscreen mode Exit fullscreen mode

See the Modern PetstoreAPI REST documentation for the complete implementation.

Find Pets by Tags

Old Swagger Petstore

GET /pet/findByTags?tags=tag1,tag2
Enter fullscreen mode Exit fullscreen mode

Modern PetstoreAPI

GET /pets?tags=friendly,trained
Enter fullscreen mode Exit fullscreen mode

The collection remains /pets; tags describes the filter.

User Login

Old Swagger Petstore

GET /user/login?username=john&[REDACTED CREDENTIAL]
Enter fullscreen mode Exit fullscreen mode

This design has several problems:

  • login is an action in the path
  • GET is 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"
}
Enter fullscreen mode Exit fullscreen mode

This design:

  • Uses POST for an operation that processes submitted credentials
  • Places credentials in the request body instead of the URL
  • Uses an /auth resource 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}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

/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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Option 1: Query Parameters

GET /pets?search=labrador
Enter fullscreen mode Exit fullscreen mode

Use this for straightforward filtering.

Option 2: A Search Resource

GET /pets/search?q=labrador
Enter fullscreen mode Exit fullscreen mode

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"
  }
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Expose the result as a resource:

GET /shipping-estimates?weight=10&destination=NY
Enter fullscreen mode Exit fullscreen mode

The client requests a shipping estimate; the URL does not need to describe the server’s internal calculation.

Batch Operations

Instead of:

POST /batchDeletePets
Enter fullscreen mode Exit fullscreen mode

Use the collection and identify the resources to delete:

DELETE /pets?ids=1,2,3
Enter fullscreen mode Exit fullscreen mode

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]
}
Enter fullscreen mode Exit fullscreen mode

State Changes

Avoid separate action endpoints:

POST /activateUser
POST /deactivateUser
Enter fullscreen mode Exit fullscreen mode

Treat the state as part of the user resource:

PATCH [REDACTED PATH]
Content-Type: application/json

{
  "status": "ACTIVE"
}
Enter fullscreen mode Exit fullscreen mode

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

  1. Import the Modern PetstoreAPI OpenAPI specification.
  2. Import your own API specification.
  3. Review the generated endpoint structure.
  4. 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.`
    );
  }
}
Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Return:

301 Moved Permanently
Location: /pets?status=available
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Prefer:

GET /pets?status=AVAILABLE
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

Another option is to model authentication as sessions:

POST /sessions
DELETE /sessions/{id}
Enter fullscreen mode Exit fullscreen mode

How should I handle complex queries?

Use query parameters for simple filters:

GET /pets?status=available&species=dog
Enter fullscreen mode Exit fullscreen mode

For complex queries, use a search resource or the QUERY method:

POST /pets/search
Enter fullscreen mode Exit fullscreen mode

What if an operation does not map to CRUD?

Model the result as a sub-resource. For example, instead of:

POST /processPayment
Enter fullscreen mode Exit fullscreen mode

use:

POST /orders/{id}/payment
Enter fullscreen mode Exit fullscreen mode

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)