PUT Requests
Table of Contents
Understanding PUT Requests
In REST APIs, PUT is one of the core HTTP methods you will use to update data on the server. It has a very specific meaning and behavior that is different from POST, PATCH, and others. In this chapter, you will learn what makes PUT special and how to use it correctly when designing and consuming REST APIs.
What Is a PUT Request?
A PUT request is an HTTP request that tells the server:
“Store the resource I am sending at this exact URL. If it already exists, replace it. If it does not exist, you may create it.”
So, PUT is usually used to create or completely replace a resource at a known URL.
A typical PUT request looks like this:
PUT /users/123 HTTP/1.1
Host: api.example.com
Content-Type: application/json
{
"id": 123,
"name": "Alice",
"email": "alice@example.com",
"is_active": true
}
The client here is saying: “The user with ID 123 should now have exactly this data. Store it at /users/123.”
Compare that with POST to /users, which usually means “create a new user, you (the server) choose the ID and URL.”
PUT vs POST vs PATCH
Understanding where PUT fits requires comparing it with POST and PATCH.
Conceptual comparison
| Method | Typical Use | Target URL pattern | Body contains | Idempotent |
|---|---|---|---|---|
| POST | Create a new resource under a collection | /users | New resource data (often partial) | No |
| PUT | Create or fully replace a resource at a URL | /users/123 | Full resource representation | Yes |
| PATCH | Partially update part of a resource | /users/123 | Only fields to change | Not guaranteed |
Key ideas:
- POST is for “add something new” to a collection. The server often decides the new resource ID.
- PUT is for “the resource at this URL should now be exactly this.”
- PATCH is for “change only these fields of the resource.”
Important rule:
Use PUT when you want to fully replace the resource representation at a known URL, and the operation should be idempotent (same request repeated has the same final effect).
Use PATCH when you want to partially update fields.
Use POST to create new resources under a collection or to trigger non-idempotent actions.
Idempotency of PUT
Idempotent means:
Performing the same operation multiple times in a row results in the same final state as doing it once.
This does not mean the responses have to be exactly the same each time, but the resource on the server must end up in the same state.
Example of idempotent PUT
Imagine a user resource:
{
"id": 1,
"name": "Alice",
"email": "alice@old.com",
"is_active": true
}You send:
PUT /users/1
Content-Type: application/json
{
"id": 1,
"name": "Alice Wonderland",
"email": "alice@new.com",
"is_active": false
}- First time: user is updated.
- Second time with the same body: user is already in that state. Nothing changes.
- Third time: still same state.
The resource after each operation is:
{
"id": 1,
"name": "Alice Wonderland",
"email": "alice@new.com",
"is_active": false
}No matter how many times you repeat this PUT, the final state is the same, so the operation is idempotent.
Why idempotency matters
Idempotency is very useful for:
- Network retries: if a client or proxy resends a request because it is not sure if the previous one succeeded, it should be safe to repeat.
- Clients with poor connectivity: they can retry until the request goes through, knowing they will not accidentally double-update or double-create something.
Compare with POST:
- Sending the same POST to
/orderstwice might create two separate orders. - Because of this, POST is usually not idempotent.
Full Replacement vs Partial Updates
The usual interpretation of PUT is full replacement.
Full replacement semantics
With full replacement:
- The body you send is considered the entire representation of the resource.
- Any fields that are not present may be treated as deleted or reset to a default.
Example: current resource
{
"id": 10,
"name": "My Task",
"description": "Do something",
"is_done": false
}You send:
PUT /tasks/10
Content-Type: application/json
{
"id": 10,
"name": "My Task (updated)"
}Possible server interpretation with full replacement:
{
"id": 10,
"name": "My Task (updated)",
"description": null,
"is_done": null
}If you want to keep other fields unchanged, you would include them in the body:
PUT /tasks/10
Content-Type: application/json
{
"id": 10,
"name": "My Task (updated)",
"description": "Do something",
"is_done": false
}
Important rule:
With PUT, the request body should usually contain the full representation of the resource.
If you only want to modify some fields and keep others unchanged, use PATCH.
Some real APIs relax this rule and treat PUT more like PATCH, but that is confusing and not fully RESTful. For clean API design, keep the meanings different and clear.
Typical URL Design for PUT
PUT almost always targets a single, specific resource.
Common patterns
| Resource type | Collection URL | Single resource URL | Example PUT |
|---|---|---|---|
| Users | /users | /users/{user_id} | PUT /users/123 |
| Products | /products | /products/{sku} | PUT /products/ABC-123 |
| Tasks | /tasks | /tasks/{task_id} | PUT /tasks/42 |
In many APIs:
POST /users→ create a new user, ID assigned by server.PUT /users/123→ create or replace user with ID 123.PATCH /users/123→ partially update user 123.DELETE /users/123→ delete user 123.
The important part: PUT uses a URL that identifies exactly one resource. The client is responsible for knowing that URL and often even the ID.
Creating vs Updating with PUT
Should you use PUT only for updates, or also for creating resources?
Two valid interpretations
There are two common approaches:
- Update only
- If the resource does not exist, respond with 404 Not Found.
- PUT is only allowed when the resource already exists.
- Create or replace (upsert-like)
- If the resource exists, replace it with the provided representation.
- If the resource does not exist, create it at that URL.
Both patterns are seen in real APIs. You should decide and document the behavior in your API.
Example: PUT as “update only”
Request:
PUT /users/123
Content-Type: application/json
{
"id": 123,
"name": "Alice",
"email": "alice@example.com"
}- If user 123 exists: update it, return 200 OK with the updated resource.
- If user 123 does not exist: return 404 Not Found.
Example: PUT as “create or replace”
Same request:
PUT /users/123
Content-Type: application/json
{
"id": 123,
"name": "Alice",
"email": "alice@example.com"
}- If user 123 exists: update, return 200 OK or 204 No Content.
- If user 123 does not exist: create user 123, return 201 Created.
Server response for creation might be:
HTTP/1.1 201 Created
Location: /users/123
Content-Type: application/json
{
"id": 123,
"name": "Alice",
"email": "alice@example.com"
}
Important rule:
If you allow PUT to create resources, return 201 Created for new resources, and include a Location header with the resource URL.
If you use PUT only for updates and the resource does not exist, return 404 Not Found.
Common Response Codes for PUT
PUT typically uses a small set of HTTP status codes.
| Status code | When to use for PUT |
|---|---|
| 200 OK | Resource was updated or created and response body is returned |
| 201 Created | Resource was newly created at this URL |
| 204 No Content | Resource was updated and no body is returned |
| 400 Bad Request | Invalid data or malformed request body |
| 401 Unauthorized | Authentication required or invalid |
| 403 Forbidden | Authenticated but not allowed to update this resource |
| 404 Not Found | Resource to update does not exist |
| 409 Conflict | Conflicting state, such as version mismatch or duplicate |
| 422 Unprocessable Entity | Validation failed for request data |
Many APIs choose between 200 and 204 for successful updates:
- Return 200 OK when you want to send back the updated representation.
- Return 204 No Content when there is nothing useful to send back.
Example:
PUT /tasks/10
Content-Type: application/json
{
"id": 10,
"title": "Buy milk",
"is_done": true
}Response with updated resource:
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 10,
"title": "Buy milk",
"is_done": true
}Or minimal response:
HTTP/1.1 204 No ContentPUT Request Examples
Example 1: Updating a user profile
URL: /users/42
Current server data:
{
"id": 42,
"username": "jdoe",
"full_name": "John Doe",
"email": "john@example.com",
"bio": "Hello world"
}
Client wants to change the full_name and bio. Since this is PUT, the client sends full data:
PUT /users/42
Content-Type: application/json
{
"id": 42,
"username": "jdoe",
"full_name": "John M. Doe",
"email": "john@example.com",
"bio": "Backend developer"
}Possible response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 42,
"username": "jdoe",
"full_name": "John M. Doe",
"email": "john@example.com",
"bio": "Backend developer"
}Example 2: Creating or replacing a product
URL: /products/ABC-123
Client:
PUT /products/ABC-123
Content-Type: application/json
{
"sku": "ABC-123",
"name": "Blue T-Shirt",
"price": 19.99,
"in_stock": true
}If the product did not exist before:
HTTP/1.1 201 Created
Location: /products/ABC-123
Content-Type: application/json
{
"sku": "ABC-123",
"name": "Blue T-Shirt",
"price": 19.99,
"in_stock": true
}If it existed:
HTTP/1.1 200 OK
Content-Type: application/json
{
"sku": "ABC-123",
"name": "Blue T-Shirt",
"price": 19.99,
"in_stock": true
}Example 3: Handling invalid PUT data
Request:
PUT /users/50
Content-Type: application/json
{
"id": 50,
"email": "not-an-email",
"username": ""
}Server validation:
emailis invalid format.usernameis required and cannot be empty.
Response:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{
"detail": [
{"field": "email", "message": "Invalid email format"},
{"field": "username", "message": "Username is required"}
]
}Designing PUT in Your REST API
When you design your own API, decide and document how PUT works in your system.
Checklist for PUT design
- Define the URL pattern
- Usually:
/resources/{id}.
Example:/tasks/{task_id}. - Decide if PUT can create
- Update only (404 if not found), or
- Create or replace (201 when created, 200 or 204 when updated).
- Use full representation in the body
- Require all mandatory fields.
- Either set missing optional fields to null or keep the previous value, but be consistent and document it.
- Use consistent status codes
- 200 or 204 for success, 201 when created.
- 400/422 for validation errors.
- 404 when resource is not found.
- 401/403 when not authenticated or not authorized.
- Keep PUT idempotent
- Repeating the same PUT should not change the resource again.
- Avoid side effects like sending emails or creating new resources as part of PUT.
- Return updated resource when helpful
- Especially useful when the server modifies data (for example sets
updated_at, normalizes strings, adds links).
Example: Task API with PUT
Resource: Task
{
"id": 1,
"title": "Buy milk",
"is_done": false,
"due_date": "2026-09-01T10:00:00Z"
}API decisions:
- URL:
/tasks/{task_id} - PUT can only update existing tasks.
- PUT expects full task representation.
- If successful: return 200 OK with updated task.
- If not found: return 404 Not Found.
Client request:
PUT /tasks/1
Content-Type: application/json
{
"id": 1,
"title": "Buy oat milk",
"is_done": true,
"due_date": "2026-09-01T10:00:00Z"
}Server response:
HTTP/1.1 200 OK
Content-Type: application/json
{
"id": 1,
"title": "Buy oat milk",
"is_done": true,
"due_date": "2026-09-01T10:00:00Z",
"updated_at": "2026-08-27T12:34:56Z"
}If task 1 did not exist:
HTTP/1.1 404 Not Found
Content-Type: application/json
{
"detail": "Task not found"
}Practical Tips and Common Pitfalls
1. Do not mix PUT semantics with PATCH
Avoid using PUT for partial updates. Some APIs do this:
PUT /users/1
Content-Type: application/json
{
"full_name": "New Name"
}
and then only update full_name. This blurs the line between PUT and PATCH and can confuse clients.
Better:
- Use PATCH for partial updates.
- Use PUT when the client sends a complete representation.
2. Consider resource versioning for concurrent updates
Two clients might try to update the same resource at nearly the same time, for example:
- Client A reads user 1, then PUT with changes.
- Client B also reads user 1, then PUT with different changes slightly later.
Without protection, the last write overwrites previous changes. Many APIs use a version field or ETag and 409 Conflict to detect this, which is part of optimistic locking, but the details belong to more advanced topics.
3. Validate IDs in body vs URL
If your resource has an id field and the URL also includes {id}, you should ensure they match.
Example:
PUT /users/123
Content-Type: application/json
{
"id": 999,
"name": "Alice"
}You can:
- Ignore the body
idand always use the URLid, or - Validate that
idin body equalsidin URL and return 400 if not.
What you choose is up to your API design, but be consistent and document it.
4. Handling missing or optional fields
Decide what happens when a client omits optional fields in PUT:
- Do you keep the old value, or
- Do you set them to null / remove them?
For strict full replacement semantics, missing fields should usually be treated as removed. But this can surprise clients, so many APIs document clearly which fields must always be sent and which ones are optional and preserved.
Summary
- PUT is used to create or fully replace a resource at a known URL.
- It is idempotent: sending the same request multiple times results in the same final state.
- PUT typically expects a full representation of the resource, unlike PATCH which is partial.
- Common behavior:
PUT /resources/{id}updates an existing resource, or sometimes creates it.- 200 / 204 on success, 201 if a new resource is created, 404 if not found, 400 / 422 for invalid data.
- Good API design keeps a clear separation between POST, PUT, and PATCH, and uses consistent URLs and status codes.
Understanding and using PUT correctly is a key step toward building clean, predictable REST APIs.
Views: 6
KAHIBARO