HTTP Methods for REST APIs: GET, POST, PUT, DELETE

How RESTful operations work: GET, POST, PUT, PATCH, DELETE, and OPTIONS

Last Updated:

Table of Contents

  1. HTTP Methods Overview
  2. GET — Retrieve Resources
  3. POST — Create Resources
  4. PUT — Replace Resources
  5. PATCH — Partial Updates
  6. DELETE — Remove Resources
  7. HEAD & OPTIONS
  8. JavaScript fetch() Examples
  9. Error Handling by Method
  10. HTTP Method Override
  11. Choosing the Right Method

Overview

HTTP methods (also called verbs) define the action to do on a resource. RFC 9110 (HTTP Semantics) defines them formally. In RESTful APIs, these methods map to CRUD operations (Create, Read, Update, Delete). They give a standardized way to interact with resources. Each method returns appropriate HTTP status codes and should follow REST best practices.

Method CRUD Idempotent Safe Request Body Response Body
GET Read ✅ Yes ✅ Yes ❌ No ✅ Yes
POST Create ❌ No ❌ No ✅ Yes ✅ Yes
PUT Update/Replace ✅ Yes ❌ No ✅ Yes Optional
PATCH Partial Update ❌ No* ❌ No ✅ Yes ✅ Yes
DELETE Delete ✅ Yes ❌ No Optional Optional
OPTIONS N/A ✅ Yes ✅ Yes ❌ No Optional

*PATCH can be idempotent. This depends on the implementation.

GET - Retrieve Resources

GET
Safe Idempotent Cacheable

The GET method retrieves data from the server. It should never modify server state and is considered a "safe" method. GET requests can be cached, bookmarked, and remain in browser history.

When to Use GET

  • To fetch a list of resources
  • To retrieve a single resource by ID
  • To search or filter resources
  • To load data for display

Examples

Get All Users

GET /api/v1/users
{
  "data": [
    {"id": 1, "name": "John Doe", "email": "john@example.com"},
    {"id": 2, "name": "Jane Smith", "email": "jane@example.com"}
  ],
  "pagination": {"page": 1, "total": 50}
}

Get Single User

GET /api/v1/users/123
{
  "id": 123,
  "name": "John Doe",
  "email": "john@example.com",
  "created_at": "2023-01-15T10:30:00Z"
}

Get with Query Parameters

GET /api/v1/users?status=active&role=admin&page=2&limit=10
{
  "data": [...],
  "pagination": {"page": 2, "limit": 10, "total": 150}
}

Best Practices

  • Never use GET for operations that modify data
  • Keep URLs under 2048 characters
  • Use query parameters for filtering, sorting, and pagination
  • Return appropriate cache headers

POST - Create Resources

POST
Not Safe Not Idempotent

The POST method creates new resources on the server. Each POST request can create a new resource. Thus POST is not idempotent. Two identical POST requests create two resources.

When to Use POST

  • To create a new resource
  • To submit form data
  • To upload files
  • To start complex operations that do not fit other methods

Examples

Create User

POST /api/v1/users

Request Body:

{
  "name": "Jane Smith",
  "email": "jane@example.com",
  "password": "securepassword123"
}

Response (201 Created):

{
  "id": 124,
  "name": "Jane Smith",
  "email": "jane@example.com",
  "created_at": "2023-06-20T14:30:00Z"
}

Create Order

POST /api/v1/orders

Request Body:

{
  "user_id": 123,
  "items": [
    {"product_id": 456, "quantity": 2},
    {"product_id": 789, "quantity": 1}
  ],
  "shipping_address": "123 Main St, City, Country"
}

Best Practices

  • Return 201 Created with the new resource in the response body
  • Include a Location header that points to the new resource
  • Validate all input data before you create the resource
  • Use idempotency keys for financial transactions

PUT - Update/Replace Resources

PUT
Not Safe Idempotent

The PUT method replaces an entire resource with the data you supply. PUT is idempotent, thus multiple identical requests produce the same result. If a resource does not exist, PUT can create it (upsert behavior).

When to Use PUT

  • To replace an entire resource
  • To update a resource when you have all its data
  • To create a resource at a specific URL (client-determined ID)

Examples

Update User (Full Replace)

PUT /api/v1/users/123

Request Body:

{
  "name": "John Doe Updated",
  "email": "john.updated@example.com",
  "role": "admin",
  "status": "active"
}

Response (200 OK):

{
  "id": 123,
  "name": "John Doe Updated",
  "email": "john.updated@example.com",
  "role": "admin",
  "status": "active",
  "updated_at": "2023-06-20T15:00:00Z"
}

PUT vs POST

PUT POST
Idempotent Not idempotent
Client specifies the URI Server determines the URI
Replace entire resource Create new resource

PATCH - Partial Updates

PATCH
Not Safe Not Idempotent*

The PATCH method applies partial modifications to a resource. Unlike PUT, you only send the fields that need to be updated. This is more efficient for large resources where only a few fields change.

When to Use PATCH

  • To update specific fields of a resource
  • When you do not have or need the entire resource
  • Bandwidth optimization for large resources

Examples

Update User Email Only

PATCH /api/v1/users/123

Request Body:

{
  "email": "newemail@example.com"
}

Update Order Status

PATCH /api/v1/orders/456

Request Body:

{
  "status": "shipped",
  "tracking_number": "1Z999AA10123456784"
}

PUT vs PATCH

PUT PATCH
Replace entire resource Modify specific fields
Must send all fields Send only changed fields
Always idempotent Can be idempotent

Making PATCH Idempotent

PATCH is not inherently idempotent, but you can design it to be. You must make sure that the same PATCH request always produces the same state.

Non-idempotent PATCH (counter increment — avoid):

PATCH/api/v1/posts/42
{ "view_count": { "increment": 1 } }  // Each call adds 1 — NOT idempotent

Idempotent PATCH (set absolute value — prefer):

PATCH/api/v1/posts/42
{ "status": "published" }  // Same result every time — idempotent

Upsert: Create or Update with HTTP Methods

An upsert (insert + update) creates a resource if it does not exist, or updates it if it does — all in a single request. REST APIs implement upserts primarily with PUT. You can also add conditional logic.

Upsert with PUT (Recommended)

PUT is naturally suited for upserts when the client controls the resource ID. The server creates the resource if it does not exist, or replaces it if it does:

PUT Upsert — Create or Replace User Profile

PUT/api/v1/users/123/profile

Request Body:

{
  "bio": "Backend engineer",
  "location": "San Francisco",
  "website": "https://example.com"
}

Response — if created: 201 Created | if updated: 200 OK

Conditional Upsert with If-None-Match Header

PUT/api/v1/config/feature-flags
// Request headers:
If-None-Match: *    // Only create — fail if already exists (409 Conflict)
// OR
If-Match: "abc123" // Only update if ETag matches (optimistic locking)

Upsert with PATCH (Less Common)

PATCH/api/v1/users/123/settings
// Request header:
X-Upsert: true

// Body — fields to set (create if not exists, update if exists):
{
  "theme": "dark",
  "notifications": true
}

When to Use Which Approach

ScenarioMethodReason
Client knows the ID, replace entire resourcePUTIdempotent, semantically correct
Client knows the ID, update partial fieldsPATCHBandwidth efficient
Server assigns the IDPOSTServer controls URI
Sync/replicate resource statePUTFull replacement ensures consistency

DELETE - Remove Resources

DELETE
Not Safe Idempotent

The DELETE method removes a resource from the server. DELETE is idempotent. Two or more DELETE requests for the same resource have the same effect as one request. The resource no longer exists.

When to Use DELETE

  • To remove a resource permanently
  • To cancel orders or subscriptions
  • To revoke access tokens

Examples

Delete User

DELETE /api/v1/users/123

Response (204 No Content or 200 OK):

// 204 No Content - empty body
// or 200 OK with confirmation:
{
  "message": "User successfully deleted",
  "deleted_at": "2023-06-20T16:00:00Z"
}

Delete with Soft Delete

DELETE /api/v1/posts/789

Response (200 OK):

{
  "id": 789,
  "status": "deleted",
  "deleted_at": "2023-06-20T16:00:00Z",
  "recoverable_until": "2023-07-20T16:00:00Z"
}

Best Practices

  • Return 204 No Content for successful deletion
  • Return 404 if the resource does not exist (or 204 for idempotency)
  • Consider soft deletes for recoverable data
  • Be careful with cascade deletes

OPTIONS - Get Options

OPTIONS
Safe Idempotent

The OPTIONS method describes the communication options for a target resource. Clients commonly use OPTIONS in CORS (Cross-Origin Resource Sharing) preflight requests. These requests check which methods and headers are permitted.

When OPTIONS is Used

  • CORS preflight requests
  • To discover the permitted methods for a resource
  • API introspection and documentation

Examples

CORS Preflight Request

OPTIONS /api/v1/users

Response Headers:

HTTP/1.1 204 No Content
Allow: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Origin: *
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400

JavaScript fetch() Examples for Every HTTP Method

The browser's fetch() API is the modern way to call REST endpoints from JavaScript. Here are complete, copy-paste examples for each HTTP method, including proper headers and error handling.

GET — Fetch a resource

// GET /api/v1/users/123
const response = await fetch('https://api.example.com/v1/users/123', {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Authorization': 'Bearer YOUR_TOKEN'
  }
});

if (!response.ok) {
  throw new Error(`HTTP error: ${response.status}`);
}
const user = await response.json();
console.log(user);

POST — Create a resource

// POST /api/v1/users
const response = await fetch('https://api.example.com/v1/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_TOKEN'
  },
  body: JSON.stringify({
    name: 'Jane Smith',
    email: 'jane@example.com'
  })
});

if (!response.ok) {
  const error = await response.json();
  throw new Error(error.message);
}
const newUser = await response.json(); // 201 Created
console.log('Created:', newUser.id);

PUT — Replace a resource

// PUT /api/v1/users/123 — full replacement, all fields required
const response = await fetch('https://api.example.com/v1/users/123', {
  method: 'PUT',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_TOKEN'
  },
  body: JSON.stringify({
    name: 'Jane Smith',
    email: 'jane@example.com',
    role: 'admin',
    status: 'active'
  })
});

const updated = await response.json(); // 200 OK

PATCH — Partial update

// PATCH /api/v1/users/123 — only the fields you want to change
const response = await fetch('https://api.example.com/v1/users/123', {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': 'Bearer YOUR_TOKEN'
  },
  body: JSON.stringify({
    email: 'newemail@example.com'  // only changed field
  })
});

const updated = await response.json(); // 200 OK

DELETE — Remove a resource

// DELETE /api/v1/users/123
const response = await fetch('https://api.example.com/v1/users/123', {
  method: 'DELETE',
  headers: {
    'Authorization': 'Bearer YOUR_TOKEN'
  }
});

if (response.status === 204) {
  console.log('Deleted successfully');        // 204 No Content — no body
} else if (response.status === 404) {
  console.log('User not found');
}

Reusable fetch() Wrapper

For real applications, wrap fetch in a utility that handles auth headers and error parsing automatically:

async function apiFetch(path, options = {}) {
  const response = await fetch(`https://api.example.com${path}`, {
    ...options,
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${getToken()}`,
      ...options.headers,
    },
    body: options.body ? JSON.stringify(options.body) : undefined,
  });

  if (!response.ok) {
    const err = await response.json().catch(() => ({ message: response.statusText }));
    throw Object.assign(new Error(err.message), { status: response.status });
  }

  return response.status === 204 ? null : response.json();
}

// Usage
const user   = await apiFetch('/v1/users/123');                                    // GET
const created = await apiFetch('/v1/users', { method: 'POST', body: { name: 'Ana' } }); // POST
await apiFetch('/v1/users/123', { method: 'DELETE' });                             // DELETE

Error Handling by HTTP Method

Each HTTP method has a predictable set of error responses. Knowing which status codes to expect — and how to handle them — is essential for building robust API clients.

Method Common Error Status Cause Client Action
GET 404 Not Found Resource does not exist Show empty state, do not retry
GET 304 Not Modified ETag / If-None-Match matched Use cached response (not an error)
POST 409 Conflict Duplicate resource (e.g. email taken) Show field-level validation error
POST 422 Unprocessable Entity Validation failed (bad field values) Display errors per field from response body
PUT 404 Not Found Resource to replace does not exist Create it via POST or handle upsert
PUT 412 Precondition Failed If-Match ETag mismatch (concurrent edit) Re-fetch, merge, retry (optimistic lock)
PATCH 422 Unprocessable Entity Invalid field values in patch body Show validation error from response
DELETE 404 Not Found Resource already deleted or never existed Treat as success (idempotent intent)
DELETE 409 Conflict Resource has dependent children blocking delete Delete children first or use cascade param
Any 405 Method Not Allowed Endpoint does not support this method Check Allow header for valid methods
Any 429 Too Many Requests Rate limit exceeded Back off using Retry-After header

Always Inspect the Response Body on Errors

A good REST API returns structured error details in the body, not just a status code. Always parse the error body — it tells you which field failed validation, what the conflict is, and how to fix it. See our Error Handling guide for the recommended error response format.

Example 422 error body

{
  "error": "validation_failed",
  "message": "Request validation failed",
  "details": [
    { "field": "email", "message": "Email is already taken" },
    { "field": "name",  "message": "Name must be at least 2 characters" }
  ]
}

HTTP Method Override

Some environments — legacy firewalls, certain corporate proxies, older HTML forms — only allow GET and POST requests. The HTTP Method Override pattern lets clients tunnel PUT, PATCH, and DELETE requests through POST using a special header.

The X-HTTP-Method-Override Header

Send a POST request with the X-HTTP-Method-Override header set to the intended method. The server reads the header and routes the request as if it received a PUT, PATCH, or DELETE:

Simulating PUT via POST

POST /api/v1/users/123 HTTP/1.1
Content-Type: application/json
X-HTTP-Method-Override: PUT

{
  "name": "Jane Smith",
  "email": "jane@example.com",
  "role": "admin"
}
// Server treats this as: PUT /api/v1/users/123

Simulating DELETE via POST

POST /api/v1/users/123 HTTP/1.1
X-HTTP-Method-Override: DELETE

// Server treats this as: DELETE /api/v1/users/123

Alternative: _method query parameter

POST /api/v1/users/123?_method=DELETE HTTP/1.1

// Some frameworks (Rails, Laravel) also support the _method query string

When to Use Method Override

  • HTML forms — forms only support GET and POST. Use a hidden _method field for PUT and DELETE
  • Restrictive proxies — corporate networks that block non-GET/POST requests
  • Legacy client libraries — older HTTP clients that do not support all verbs

Important: Only enable method override on the server if you actually need it. It is an escape hatch — modern clients using proper HTTP have no need for it. Always validate that the overridden method is permitted for that endpoint.

Additional Methods

HEAD

Get Headers Only

Identical to GET, but returns only response headers and no body. HEAD is safe and idempotent, like GET. Use it to check that a resource exists, to read metadata (Content-Length, Last-Modified, ETag), or to validate cache freshness. HEAD does not have the bandwidth cost of a full download.

HEAD /api/users/123

Practical HEAD use cases

  • Existence check: HEAD /files/report.pdf — 200 means it exists, 404 means it does not
  • Content-Length: Check file size before deciding whether to download
  • Cache validation: Compare ETag or Last-Modified before re-fetching

HEAD with curl

curl -I https://api.example.com/v1/files/report.pdf

# Response headers only:
HTTP/2 200
content-type: application/pdf
content-length: 204800
etag: "abc123"
last-modified: Mon, 10 Jun 2026 08:00:00 GMT
TRACE

Loop-Back Test

Performs a message loop-back test to the target resource. Use it to debug. Most servers disable TRACE in production for security.

TRACE /api/debug
CONNECT

Establish Tunnel

Establishes a tunnel to the server identified by the target resource. Used primarily for HTTPS connections through proxies.

CONNECT example.com:443

Common Mistakes with HTTP Methods

Even experienced developers make mistakes with HTTP method semantics. Here are the most common pitfalls to avoid.

Using POST for Everything (RPC-Style Anti-Pattern)

Some developers use POST for all operations, and treat the API like Remote Procedure Calls. This breaks caching and idempotency guarantees. It also makes the API more difficult to understand.

❌ Bad (RPC-style) POST /api/getUser?id=123
POST /api/deleteOrder?id=456
POST /api/updateUserStatus
✅ Good (REST-style) GET /api/users/123
DELETE /api/orders/456
PATCH /api/users/123 { "status": "active" }

Sending GET Requests with a Body

HTTP does not forbid a body in a GET request, but we do not recommend it. Many proxies, CDNs, and HTTP clients ignore or strip GET request bodies. Use query parameters instead.

❌ Bad GET /api/users
Body: { "filter": { "status": "active" } }
✅ Good GET /api/users?status=active

Ignoring Idempotency in Distributed Systems

In distributed systems, network failures can cause clients to retry requests. PUT and DELETE are idempotent, thus you can safely retry them. POST is not idempotent. If you retry a POST request to create a user, you create duplicate users. Use idempotency keys for POST operations in critical paths.

✅ Good — Idempotency key for POST POST /api/payments
Idempotency-Key: unique-client-generated-uuid

Using DELETE Without Considering Cascades

A DELETE request often has side effects on related resources. A deleted user can have orders, comments, and uploaded files. Plan cascade behavior explicitly and record it. Consider soft deletes for recoverable data.

✅ Good — Document cascade behavior DELETE /api/users/123
// Deletes user, anonymizes their orders, removes personal data
// Returns 200 with summary of what was deleted

Frequently Asked Questions

What are the main HTTP methods used in REST APIs?

The main HTTP methods in REST APIs are:

  • GET — Retrieve a resource (safe, idempotent)
  • POST — Create a new resource
  • PUT — Replace an entire resource (idempotent)
  • PATCH — Partially update a resource
  • DELETE — Remove a resource (idempotent)
  • OPTIONS — Describe allowed operations on a resource
What is the difference between PUT and PATCH?

PUT replaces the entire resource (all fields required). PATCH applies partial changes (only send changed fields). PUT is always idempotent. PATCH can be idempotent, but this depends on your implementation. For upsert patterns, see the Upsert section above.

Is GET idempotent?

Yes. GET is both safe (no side effects) and idempotent (same result every call). GET responses can also be cached. You should never use GET for operations that modify data on the server.

When should I use POST instead of PUT?

Use POST when the server assigns the resource ID or URL. For example, the server generates the ID for a new user. Use PUT when the client knows the exact URL and must create or replace the resource at that location.

What does idempotent mean in REST APIs?

An idempotent operation produces the same result no matter how many times you call it. GET, PUT, DELETE, and OPTIONS are idempotent. POST is not idempotent, because two POST requests create two resources. Idempotency is critical in distributed systems where network failures can cause retries.