Loading content...
If resources are the nouns of REST, HTTP methods are the verbs. While resources identify what you're interacting with, HTTP methods express what action you want to perform. This separation is fundamental to REST's uniform interface—rather than defining custom operations for every resource, we use a standard set of methods with well-defined semantics.
Understanding HTTP method semantics isn't just about knowing that GET retrieves and DELETE removes. It's about understanding the contracts these methods provide: Which methods are safe to retry? Which can be cached? Which might change server state? These semantics enable infrastructure like caches, load balancers, and proxies to make intelligent decisions without understanding your specific API.
In this page, we'll explore each HTTP method in depth—its purpose, its guarantees, its proper usage, and the common mistakes developers make. Mastering method semantics is essential for building APIs that behave predictably and integrate well with the broader HTTP ecosystem.
By the end of this page, you will understand the semantics of each HTTP method—GET, POST, PUT, PATCH, DELETE, and others. You'll know which methods are safe, which are idempotent, and how these properties affect caching, retries, and error handling. You'll be able to select the correct method for any operation and explain why.
HTTP defines a set of methods (sometimes called "verbs") that indicate the desired action to be performed on a resource. Each method has specific semantic properties that determine how it should behave and how clients and intermediaries can interact with it.
Every HTTP method is characterized by two important properties:
1. Safety
A method is safe if it doesn't modify resource state. Safe methods are for retrieval and inspection—they have no side effects. Clients can call safe methods without worrying about unintended changes.
Safe methods: GET, HEAD, OPTIONS, TRACE
2. Idempotency
A method is idempotent if making the same request multiple times produces the same result as making it once. The server state after N identical requests is the same as after 1 request. This property enables safe retries.
Idempotent methods: GET, HEAD, OPTIONS, TRACE, PUT, DELETE
Non-idempotent method: POST, PATCH (typically)
| Method | Safe | Idempotent | Request Body | Response Body | Typical Use |
|---|---|---|---|---|---|
| GET | ✅ Yes | ✅ Yes | No | Yes | Retrieve resource representation |
| HEAD | ✅ Yes | ✅ Yes | No | No | Get headers only |
| OPTIONS | ✅ Yes | ✅ Yes | Optional | Yes | Get allowed methods |
| POST | ❌ No | ❌ No | Yes | Yes | Create resource / trigger action |
| PUT | ❌ No | ✅ Yes | Yes | Optional | Replace resource entirely |
| PATCH | ❌ No | Varies* | Yes | Optional | Partial update |
| DELETE | ❌ No | ✅ Yes | Optional | Optional | Remove resource |
PATCH is not inherently idempotent—it depends on how it's implemented. A PATCH that says 'set name to X' is idempotent. A PATCH that says 'increment counter by 1' is not. Design your PATCH operations to be idempotent when possible.
GET is the most common HTTP method. It retrieves a representation of the specified resource without modifying server state.
GET /users # Retrieve collection of users
GET /users/123 # Retrieve specific user
GET /users/123/orders # Retrieve user's orders
GET /users?status=active # Retrieve filtered collection
GET /products?q=laptop # Search products
12345678910111213141516171819202122232425262728293031
# Request: Get a specific userGET /api/v1/users/123 HTTP/1.1Host: api.example.comAccept: application/json # Response: User foundHTTP/1.1 200 OKContent-Type: application/jsonCache-Control: max-age=3600ETag: "abc123" { "id": 123, "name": "Alice Thompson", "email": "alice@example.com", "createdAt": "2024-01-15T10:30:00Z", "links": { "self": "/users/123", "orders": "/users/123/orders" }} # Conditional request (with caching)GET /api/v1/users/123 HTTP/1.1Host: api.example.comAccept: application/jsonIf-None-Match: "abc123" # Response: Not modified (use cached version)HTTP/1.1 304 Not ModifiedETag: "abc123"A common anti-pattern is using GET for actions: GET /users/123/delete. This violates GET's safety guarantee, breaks caching, and allows unintended actions from crawlers, prefetching, or browser history. State-changing operations must use POST, PUT, PATCH, or DELETE.
When GETting a collection, include pagination and filtering capabilities:
GET /users?page=2&per_page=20&sort=created_at&order=desc
Response should include:
POST submits data to be processed by the identified resource. Its most common use in REST APIs is creating new resources, but it also handles operations that don't fit other methods.
POST /users # Create a new user
POST /orders # Create a new order
POST /users/123/orders # Create order for user 123
POST /emails/456/send # Trigger sending (action)
POST /reports/generate # Trigger report generation
Location header with URI of new resource1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950
# Request: Create a new userPOST /api/v1/users HTTP/1.1Host: api.example.comContent-Type: application/jsonAccept: application/json { "name": "Bob Wilson", "email": "bob@example.com", "role": "developer"} # Response: User createdHTTP/1.1 201 CreatedContent-Type: application/jsonLocation: /api/v1/users/456 { "id": 456, "name": "Bob Wilson", "email": "bob@example.com", "role": "developer", "createdAt": "2024-06-15T14:30:00Z", "links": { "self": "/users/456", "orders": "/users/456/orders" }} # Asynchronous operationPOST /api/v1/reports/generate HTTP/1.1Host: api.example.comContent-Type: application/json { "type": "sales", "dateRange": {"start": "2024-01-01", "end": "2024-06-30"}} # Response: Accepted for processingHTTP/1.1 202 AcceptedContent-Type: application/jsonLocation: /api/v1/reports/jobs/789 { "jobId": "789", "status": "processing", "estimatedCompletionTime": "2024-06-15T14:35:00Z", "statusUrl": "/reports/jobs/789"}Consider creating an order:
POST /orders
{"product": "laptop", "quantity": 1}
If this request is sent twice (perhaps due to network retry), two separate orders are created. This is correct behavior—POST is intentionally non-idempotent. To make creation idempotent, use a client-generated ID:
PUT /orders/client-generated-uuid
{"product": "laptop", "quantity": 1}
Or include an idempotency key in headers:
POST /orders
Idempotency-Key: unique-request-id-12345
{"product": "laptop", "quantity": 1}
When POST creates a resource, always include a Location header pointing to the new resource's URI. This allows clients to immediately access or reference the created resource without parsing the response body.
PUT replaces the entire resource at the specified URI with the provided representation. If the resource doesn't exist, PUT creates it (when the server supports this).
PUT /users/123 # Replace user 123 entirely
PUT /articles/my-article # Create or replace article
PUT /config/settings # Replace entire configuration
1234567891011121314151617181920212223242526272829303132333435363738394041424344
# Request: Replace a user entirelyPUT /api/v1/users/123 HTTP/1.1Host: api.example.comContent-Type: application/json { "name": "Alice Thompson-Martinez", "email": "alice.m@example.com", "role": "senior-developer", "department": "engineering"} # Response: User replacedHTTP/1.1 200 OKContent-Type: application/json { "id": 123, "name": "Alice Thompson-Martinez", "email": "alice.m@example.com", "role": "senior-developer", "department": "engineering", "updatedAt": "2024-06-15T15:30:00Z"} # PUT with conditional update (optimistic locking)PUT /api/v1/users/123 HTTP/1.1Host: api.example.comContent-Type: application/jsonIf-Match: "etag-abc123" { "name": "Alice Updated", "email": "alice@example.com"} # Response: Conflict (someone else modified it)HTTP/1.1 412 Precondition FailedContent-Type: application/json { "error": "Resource was modified by another request", "currentETag": "etag-xyz789"}PUT is a full replacement. If the user has fields name, email, and role, and you PUT with only name and email, the role field will be removed (or reset to default). For partial updates, use PATCH instead.
PATCH applies partial modifications to a resource. Unlike PUT, which replaces the entire resource, PATCH updates only the specified fields.
PATCH /users/123 # Update specific fields of user
PATCH /orders/456/status # Update order status only
PATCH /articles/789 # Update article metadata
There are multiple ways to specify patches:
1. Merge Patch (RFC 7396) — Most common and intuitive
{"name": "New Name", "email": "new@example.com"}
Content-Type: application/merge-patch+json
2. JSON Patch (RFC 6902) — More powerful, explicit operations
[
{"op": "replace", "path": "/name", "value": "New Name"},
{"op": "add", "path": "/tags/-", "value": "new-tag"}
]
Content-Type: application/json-patch+json
123456789101112131415161718192021222324252627282930
# Merge Patch: Simple field updatesPATCH /api/v1/users/123 HTTP/1.1Host: api.example.comContent-Type: application/merge-patch+json { "email": "alice.new@example.com", "role": "tech-lead"} # ResponseHTTP/1.1 200 OKContent-Type: application/json { "id": 123, "name": "Alice Thompson", "email": "alice.new@example.com", "role": "tech-lead", "department": "engineering", "updatedAt": "2024-06-15T16:00:00Z"} # Setting a field to null (removes it)PATCH /api/v1/users/123 HTTP/1.1Content-Type: application/merge-patch+json { "nickname": null}PATCH can be non-idempotent. Consider:
{"views": {"$increment": 1}}
Calling this twice increments views twice—not idempotent.
To ensure idempotency:
{"views": 150} instead of {"$increment": 1}test operations in JSON Patch to verify initial stateUse PUT when the client has the complete resource and wants to replace it entirely. Use PATCH when the client wants to modify specific fields without affecting others. Most user-facing updates (changing email, updating profile) are better as PATCH because they avoid accidentally clearing unrelated fields.
DELETE removes the specified resource from the server.
DELETE /users/123 # Delete user 123
DELETE /orders/456/items/789 # Delete item from order
DELETE /sessions/current # Log out (delete current session)
1234567891011121314151617181920212223242526272829303132
# Simple deleteDELETE /api/v1/users/123 HTTP/1.1Host: api.example.com # Response: No ContentHTTP/1.1 204 No Content # Delete with confirmation in responseDELETE /api/v1/users/123 HTTP/1.1Host: api.example.comAccept: application/json # Response: Deleted resource returnedHTTP/1.1 200 OKContent-Type: application/json { "id": 123, "name": "Alice Thompson", "deletedAt": "2024-06-15T16:30:00Z", "message": "User successfully deleted"} # Idempotent delete (second request for same resource)DELETE /api/v1/users/123 HTTP/1.1Host: api.example.com # Option 1: Return 404 (not found)HTTP/1.1 404 Not Found # Option 2: Return 204 (idempotent - already deleted)HTTP/1.1 204 No ContentReal-world APIs often implement soft delete (marking as deleted) rather than hard delete (actual removal):
Hard Delete:
Soft Delete:
deletedAt timestamp# Soft delete - mark as deleted
DELETE /users/123 → Sets deletedAt: "2024-06-15T16:30:00Z"
# Get deleted user (if API supports it)
GET /users/123?includeSoftDeleted=true
# Restore (undelete)
POST /users/123/restore or PATCH /users/123 {deletedAt: null}
When a parent resource has children, decide what happens on delete: cascade (delete children too), restrict (prevent delete if children exist), or orphan (leave children). Document this behavior clearly—unexpected cascading deletes are a common cause of data loss.
While GET, POST, PUT, PATCH, and DELETE cover most API operations, other HTTP methods are useful in specific scenarios.
HEAD is identical to GET but returns only headers, no body. Use it to:
HEAD /files/large-report.pdf
→ Returns Content-Length: 15000000, but no body
OPTIONS returns the allowed methods for a resource. It's crucial for CORS preflight requests:
OPTIONS /users/123
→ Allow: GET, PUT, PATCH, DELETE, HEAD, OPTIONS
→ Access-Control-Allow-Methods: GET, PUT, PATCH, DELETE
TRACE echoes the received request back to the client. Useful for debugging proxy chains, but typically disabled for security reasons.
| Method | Purpose | Safe | Idempotent | Common Use Case |
|---|---|---|---|---|
| GET | Retrieve resource | ✅ | ✅ | Fetch data |
| HEAD | Get headers only | ✅ | ✅ | Check existence, metadata |
| OPTIONS | Get allowed methods | ✅ | ✅ | CORS preflight |
| TRACE | Loop-back test | ✅ | ✅ | Debug (usually disabled) |
| POST | Submit data / create | ❌ | ❌ | Create resources, actions |
| PUT | Replace resource | ❌ | ✅ | Full update |
| PATCH | Partial update | ❌ | Varies | Partial update |
| DELETE | Remove resource | ❌ | ✅ | Delete resources |
| CONNECT | Establish tunnel | ❌ | ❌ | Proxy tunneling (HTTPS) |
Some clients (older browsers, proxies) only support GET and POST. APIs sometimes accept X-HTTP-Method-Override header or _method query parameter to tunnel other methods through POST. Example: POST /users/123?_method=DELETE or POST with header X-HTTP-Method-Override: DELETE.
Choosing the right HTTP method depends on what you're trying to accomplish. Use this decision guide:
| Operation | Method | URI Pattern | Notes |
|---|---|---|---|
| List all resources | GET | /resources | With pagination |
| Get single resource | GET | /resources/{id} | |
| Search/filter resources | GET | /resources?query=x | Query parameters |
| Create resource (server ID) | POST | /resources | Return 201 + Location |
| Create resource (client ID) | PUT | /resources/{id} | Idempotent creation |
| Replace resource entirely | PUT | /resources/{id} | All fields required |
| Update specific fields | PATCH | /resources/{id} | Only changed fields |
| Delete resource | DELETE | /resources/{id} | Return 204 or 200 |
| Trigger action | POST | /resources/{id}/action | Non-CRUD operations |
| Check existence | HEAD | /resources/{id} | No body returned |
HTTP methods provide a standard vocabulary for resource operations. Understanding their semantics is essential for building predictable, scalable APIs.
What's next:
We've covered resources (nouns) and methods (verbs). The final piece is Status Codes and Responses—how your API communicates the outcome of each operation. Proper status codes make APIs debuggable, automatable, and developer-friendly.
You now understand HTTP method semantics deeply—not just what each method does, but what guarantees it provides. This knowledge enables you to select the right method for every operation and build APIs that work correctly with caches, proxies, and retry logic.