- Home
- HTTP Status Codes
- 428 Precondition Required
Status Code
428 Precondition Required
The server requires the request to be conditional. Learn when to use 428 Precondition Required to prevent lost updates and race conditions.
TL;DR: Server requires conditional headers (like If-Match) to prevent lost updates in concurrent editing. Get the resource’s ETag first, then include it in your update request.
What is 428 Precondition Required?
A 428 Precondition Required status code means the server requires the request to include conditional headers before it can be processed. Think of it like a bank requiring you to provide your current account balance before approving a withdrawal—it ensures you’re working with up-to-date information and prevents conflicting changes.
This status code is used to prevent the “lost update problem” where multiple clients might modify a resource simultaneously, potentially overwriting each other’s changes.
When Does This Happen?
You’ll see a 428 Precondition Required response in these common situations:
1. Updating Shared Resources
Multiple users editing the same document
→ Server requires If-Match header
→ Ensures you have the latest version
→ Prevents overwriting others' changes
2. Critical Data Modifications
Updating financial records
→ Requires If-Unmodified-Since
→ Confirms data hasn't changed
→ Prevents race conditions
3. API Rate Limiting Protection
High-frequency API endpoint
→ Requires conditional headers
→ Prevents accidental duplicate requests
→ Protects server resources
4. Cache Validation Required
Critical resource update
→ Server demands If-None-Match
→ Ensures client has current version
→ Prevents stale data updates
5. Distributed System Coordination
Microservices updating shared state
→ Requires version headers
→ Maintains consistency
→ Prevents conflicts
Example Responses
Basic 428 Response:
HTTP/1.1 428 Precondition Required
Content-Type: application/json
Content-Length: 156
{
"error": "Precondition Required",
"message": "This request must be conditional. Please include an If-Match or If-Unmodified-Since header.",
"required_headers": ["If-Match", "If-Unmodified-Since"]
}
```text
**With ETag Information:**
```http
HTTP/1.1 428 Precondition Required
Content-Type: application/json
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4"
{
"error": "Precondition Required",
"message": "To update this resource, include an If-Match header with the current ETag",
"current_etag": "33a64df551425fcc55e4d42a148795d9f25f89d4",
"example": "If-Match: \"33a64df551425fcc55e4d42a148795d9f25f89d4\""
}
Detailed Guidance:
HTTP/1.1 428 Precondition Required
Content-Type: application/json
Link: <https://api.example.com/docs/conditional-requests>; rel="help"
{
"error": "Precondition Required",
"message": "This endpoint requires conditional request headers to prevent lost updates",
"details": {
"reason": "Resource can be modified by multiple clients",
"required_headers": [
{
"name": "If-Match",
"description": "Include the ETag from your last GET request",
"example": "If-Match: \"abc123\""
},
{
"name": "If-Unmodified-Since",
"description": "Include the Last-Modified date from your last GET request",
"example": "If-Unmodified-Since: Wed, 21 Oct 2025 07:28:00 GMT"
}
]
},
"documentation": "https://api.example.com/docs/conditional-requests"
}
```text
## Real-World Example
Imagine you're updating a user profile through an API:
**Initial Request (Missing Precondition):**
```http
PUT /api/users/12345 HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
{
"name": "John Doe",
"email": "john.doe@example.com",
"role": "admin"
}
428 Response:
HTTP/1.1 428 Precondition Required
Content-Type: application/json
Cache-Control: no-cache
Content-Length: 423
{
"error": "Precondition Required",
"message": "User profile updates require conditional headers to prevent conflicts",
"details": {
"reason": "This resource can be modified by the user and administrators simultaneously",
"solution": "First, GET the current resource to obtain the ETag, then include it in your PUT request"
},
"steps": [
"1. GET /api/users/12345 to retrieve current data and ETag",
"2. Include 'If-Match: <etag>' header in your PUT request",
"3. Retry your PUT request with the If-Match header"
],
"example": {
"header": "If-Match",
"value": "\"33a64df551425fcc55e4d42a148795d9f25f89d4\""
}
}
```text
**Corrected Request Flow:**
**Step 1: Get Current Resource**
```http
GET /api/users/12345 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
Response:
HTTP/1.1 200 OK
ETag: "v1.2.3"
Last-Modified: Wed, 18 Jan 2026 10:00:00 GMT
{
"id": "12345",
"name": "John Smith",
"email": "john.smith@example.com",
"role": "user"
}
Step 2: Update With Precondition
PUT /api/users/12345 HTTP/1.1
Host: api.example.com
Content-Type: application/json
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
If-Match: "v1.2.3"
{
"name": "John Doe",
"email": "john.doe@example.com",
"role": "admin"
}
Response:
HTTP/1.1 200 OK
ETag: "v1.2.4"
{
"id": "12345",
"name": "John Doe",
"email": "john.doe@example.com",
"role": "admin",
"updated_at": "2026-01-18T10:05:00Z"
}
```text
## 428 vs Other Conditional Codes
| Code | Meaning | Scenario | Solution |
| ------- | --------------------- | -------------------------------------- | ----------------------------------- |
| **428** | Precondition Required | No conditional header provided | Add If-Match or If-Unmodified-Since |
| **412** | Precondition Failed | Conditional header provided but failed | Get latest version and retry |
| **409** | Conflict | Request conflicts with current state | Resolve conflict manually |
| **304** | Not Modified | Conditional GET shows no change | Use cached version |
## Important Characteristics
**Prevents Lost Updates:**
```text
User A reads resource (version 1)
User B reads resource (version 1)
User A updates to version 2 ✓
User B updates (would overwrite A's changes)
→ 428 forces B to get version 2 first
```text
**Server Policy Decision:**
```http
HTTP/1.1 428 Precondition Required
↑
Server decides which endpoints require conditions
Common Required Headers:
If-Match: Requires specific ETagIf-Unmodified-Since: Requires resource unchanged since dateIf-None-Match: For conditional creation- Custom version headers
Common Mistakes
❌ Not providing guidance
HTTP/1.1 428 Precondition Required
Precondition Required ← Unhelpful, what precondition?
```text
**❌ Requiring conditions on read-only requests**
```http
GET /api/users/123
HTTP/1.1 428 Precondition Required ← Bad: GET shouldn't require conditions
❌ Not documenting which headers are needed
{
"error": "Precondition Required"
← Missing: which headers? which values?
}
```text
**✅ Correct usage**
```http
HTTP/1.1 428 Precondition Required
Content-Type: application/json
{
"error": "Precondition Required",
"message": "Include If-Match header with current ETag",
"required_header": "If-Match",
"get_etag_from": "GET /api/users/123",
"current_etag": "\"abc123\""
}
Best Practices
Provide Current ETag:
app.put('/api/resource/:id', async (req, res) => {
if (!req.headers['if-match']) {
const resource = await db.getResource(req.params.id)
return res.status(428).json({
error: 'Precondition Required',
message: 'Include If-Match header to update this resource',
current_etag: resource.etag,
example: `If-Match: "${resource.etag}"`
})
}
// Process conditional update...
})
```text
**Document Required Headers:**
```http
HTTP/1.1 428 Precondition Required
Content-Type: application/json
{
"error": "Precondition Required",
"required_headers": {
"If-Match": {
"description": "ETag from your last GET request",
"example": "If-Match: \"v1.2.3\"",
"obtain_from": "GET /api/resource/:id"
}
},
"documentation": "https://docs.example.com/conditional-requests"
}
Apply to Critical Endpoints Only:
const requiresPrecondition = [
'PUT /api/users/:id',
'PATCH /api/documents/:id',
'DELETE /api/critical-data/:id'
]
// Don't require for less critical operations
const optional = ['POST /api/logs', 'GET /api/*', 'POST /api/analytics']
```javascript
**Consistent Error Format:**
```javascript
function preconditionRequiredError(resourceType, etag) {
return {
error: 'Precondition Required',
message: `${resourceType} updates require conditional headers`,
required_headers: ['If-Match', 'If-Unmodified-Since'],
current_etag: etag,
steps: [
`1. GET the current ${resourceType}`,
'2. Note the ETag header in the response',
'3. Include If-Match: <etag> in your update request'
]
}
}
Implementation Examples
Express.js Middleware:
const requireConditional = (req, res, next) => {
const requiresCondition = ['PUT', 'PATCH', 'DELETE'].includes(req.method)
const hasCondition = req.headers['if-match'] || req.headers['if-unmodified-since']
if (requiresCondition && !hasCondition) {
return res.status(428).json({
error: 'Precondition Required',
message: 'This operation requires a conditional header',
required_headers: ['If-Match', 'If-Unmodified-Since'],
hint: `GET ${req.path} to obtain the current ETag`
})
}
next()
}
app.put('/api/users/:id', requireConditional, async (req, res) => {
// Handle conditional update...
})
```javascript
**Django:**
```python
from django.http import JsonResponse
from django.views.decorators.http import require_http_methods
@require_http_methods(["PUT"])
def update_user(request, user_id):
if_match = request.headers.get('If-Match')
if not if_match:
return JsonResponse({
'error': 'Precondition Required',
'message': 'Include If-Match header with current ETag',
'required_header': 'If-Match',
'get_etag_from': f'/api/users/{user_id}'
}, status=428)
# Process conditional update
user = User.objects.get(id=user_id)
if user.etag != if_match.strip('"'):
return JsonResponse({
'error': 'Precondition Failed'
}, status=412)
# Update user...
Ruby on Rails:
class UsersController < ApplicationController
before_action :require_precondition, only: [:update, :destroy]
def update
@user = User.find(params[:id])
if stale?(etag: @user, last_modified: @user.updated_at)
if @user.update(user_params)
render json: @user
else
render json: @user.errors, status: :unprocessable_entity
end
end
end
private
def require_precondition
unless request.headers['If-Match'] || request.headers['If-Unmodified-Since']
render json: {
error: 'Precondition Required',
message: 'Include If-Match or If-Unmodified-Since header',
required_headers: ['If-Match', 'If-Unmodified-Since']
}, status: 428
end
end
end
```javascript
## Client-Side Handling
**JavaScript Fetch API:**
```javascript
async function updateUser(userId, updates) {
// First, get current resource
const getResponse = await fetch(`/api/users/${userId}`)
const etag = getResponse.headers.get('ETag')
// Then update with If-Match
const putResponse = await fetch(`/api/users/${userId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'If-Match': etag
},
body: JSON.stringify(updates)
})
if (putResponse.status === 428) {
throw new Error('Precondition required - this should not happen!')
}
if (putResponse.status === 412) {
// Resource changed, need to refetch
console.log('Resource was modified, retrying...')
return updateUser(userId, updates) // Retry
}
return putResponse.json()
}
Try It Yourself
Visit our request builder and see 428 Precondition Required:
- Set method to PUT
- Set path to /api/protected-resource
- Click Send request (without If-Match header)
- Observe 428 response
- Add If-Match header and retry
Related Status Codes
- 412 Precondition Failed - Conditional header provided but condition not met
- 409 Conflict - Request conflicts with current resource state
- 304 Not Modified - Conditional GET shows resource unchanged
- 200 OK - Successful conditional update
Frequently Asked Questions
What does 428 Precondition Required mean?
A 428 error means the server requires the request to include conditional headers like If-Match or If-Unmodified-Since. This prevents the lost update problem in concurrent editing scenarios.
How do I fix a 428 error?
First GET the resource to obtain its current ETag, then include If-Match header with that ETag in your PUT or PATCH request. This proves you are updating the version you intended to modify.
What is the lost update problem?
When two users edit the same resource simultaneously, one update can overwrite the other. Requiring preconditions ensures updates only succeed if the resource has not changed since it was read.
What is the difference between 428 and 412?
428 means you must include precondition headers but did not. 412 means you included precondition headers but they failed. 428 is missing headers; 412 is failed conditions.