Category: HTTP Methods
Description
GET and POST methods should not be used to tunnel other CRUD operations (create, update, delete) that have their own dedicated HTTP methods.
What It Checks
This rule prevents HTTP method tunneling by validating that GET and POST endpoints are not misused to perform operations that should use PUT, PATCH, or DELETE methods. It specifically checks:
- Operation summaries on GET and POST methods for keywords like "create", "update", "delete", "remove", or "add"
- Uses AI-powered analysis to determine if the operation description truly indicates tunneling (to avoid false positives)
- Ensures that CRUD operations use their semantically correct HTTP methods
The rule only flags violations when:
- The endpoint uses GET or POST
- The operation summary contains CRUD operation keywords
- AI analysis confirms the operation is actually tunneling another HTTP method
Why It Matters
Using the correct HTTP method is fundamental to RESTful API design:
- Semantic correctness: HTTP methods have well-defined meanings. GET should be safe and idempotent, POST should create new resources, DELETE should remove resources, etc.
- Caching and safety: Browsers and proxies cache GET requests and treat them as safe (no side effects). Using GET for destructive operations breaks this contract.
- Security: Tunneling DELETE operations through GET creates CSRF vulnerabilities since GET requests can be triggered by simply loading an image or clicking a link.
- Standards compliance: Violates HTTP/1.1 specification (RFC 7231) and REST architectural constraints. Proper method usage enables middleware, load balancers, and API gateways to work correctly.
Examples
Good ✅
paths:
/users:
get:
summary: List all users
responses:
'200':
description: Array of users
post:
summary: Create a new user
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/User'
responses:
'201':
description: User created
/users/{userId}:
get:
summary: Get user details
responses:
'200':
description: User object
put:
summary: Replace user
requestBody:
required: true
responses:
'200':
description: User updated
patch:
summary: Update user fields
requestBody:
required: true
responses:
'200':
description: User updated
delete:
summary: Delete user
responses:
'204':
description: User deleted
/users/{userId}/activate:
post:
summary: Activate user account
responses:
'200':
description: Account activated
Bad ❌
paths:
/users/delete/{userId}:
get: # Should use DELETE /users/{userId}
summary: Delete a user
responses:
'200':
description: User deleted
/users/create:
get: # Should use POST /users with request body
summary: Create a new user
parameters:
- name: name
in: query
- name: email
in: query
responses:
'200':
description: User created
/users/{userId}/update:
post: # Should use PUT or PATCH /users/{userId}
summary: Update user details
requestBody:
required: true
responses:
'200':
description: User updated
/users/{userId}/remove:
post: # Should use DELETE /users/{userId}
summary: Remove a user from the system
responses:
'200':
description: User removed
/products/add:
get: # Should use POST /products
summary: Add a new product
parameters:
- name: name
in: query
- name: price
in: query
responses:
'200':
description: Product added
Configuration
This rule has no configuration options. It is either enabled or disabled.
How to Fix
-
Identify the true operation: Determine what the endpoint actually does (create, read, update, delete)
-
Use the correct HTTP method:
# Before: GET tunneling DELETE GET /users/delete/{userId} # After: Proper DELETE method DELETE /users/{userId} -
Move data from query parameters to request body (for create/update operations):
# Before: GET tunneling POST with query params GET /users/create?name=John&[email protected] # After: POST with request body POST /users Content-Type: application/json { "name": "John", "email": "[email protected]" } -
Remove operation verbs from URIs:
# Before POST /users/{userId}/update # After PATCH /users/{userId} # or PUT /users/{userId} -
For controller-style actions, if the operation doesn't map to standard CRUD, use POST to a sub-resource:
# Acceptable for non-CRUD operations POST /users/{userId}/activate POST /users/{userId}/reset-password POST /orders/{orderId}/cancel
Related Rules
- Rule 3: CRUDy URIs - Prevents CRUD operation names in URI paths
- Rule 7: POST for Creation - Ensures POST is used for creating resources in collections
- Rule 53: Create Update Return - Validates proper response codes for creation and update operations