The server understands the content type and syntax of your request, but the data itself is semantically invalid. For example, the JSON is valid but contains a negative age value or an email field with a phone number. The request is well-formed but logically wrong.
HTTP 422 Unprocessable Entity is a client error status code from the WebDAV extension to HTTP, now widely adopted in modern API design. It indicates that the server understands the content type of the request body and the syntax is correct, but the semantic content is invalid. In simpler terms, the JSON or XML is well-formed, but the values it contains do not make logical sense or violate validation rules.
The distinction between 400 Bad Request and 422 Unprocessable Entity is important for API design. A 400 error means the request cannot be parsed at all, such as malformed JSON with missing brackets or an invalid Content-Type header. A 422 error means the request was successfully parsed but the data failed semantic validation, such as a negative age value, an end date before a start date, or a required field containing an empty string. This distinction helps API consumers understand whether the problem is in the request format or the request content.
Many modern API frameworks have adopted 422 as the standard response for validation failures. Ruby on Rails, Laravel, FastAPI, and Django REST Framework all use 422 by default when model validation fails. However, some APIs, particularly those following strict HTTP/1.1 conventions, use 400 for all client-side validation errors. Neither approach is wrong, but consistency within an API is essential for a good developer experience.
The request body passes syntax checks but fails business validation. For example, an age field with a value of -5, a date in the past for a future event, or a password that does not meet complexity requirements.
Individual fields are valid but their combination is not. For example, an end date that is before the start date, or a shipping address in a country that the service does not deliver to.
A field contains the wrong type of valid data. For example, an email field containing a phone number, a URL field containing an IP address, or a currency amount with too many decimal places.
The data violates a business constraint, such as ordering a quantity greater than available inventory, setting a price below the minimum allowed, or scheduling an appointment outside business hours.
The 422 response body should list which fields failed validation and why. Use these details to fix the specific fields that are semantically invalid.
Implement the same validation rules in your frontend to catch errors before sending the request. This provides instant feedback and reduces unnecessary server round trips.
Ensure emails are in email format, URLs are valid URLs, dates are in the expected format, and numbers are within acceptable ranges. Match the API's expected formats exactly.
Use 422 for semantically invalid data in a syntactically correct request. Use 400 for requests that cannot be parsed at all due to malformed syntax.
// Express.js — 422 for semantic validation errors
const express = require('express');
const app = express();
app.use(express.json());
app.post('/api/events', (req, res) => {
const { title, startDate, endDate, maxAttendees } = req.body;
const errors = {};
// Semantic validation (syntax is fine, logic is not)
const start = new Date(startDate);
const end = new Date(endDate);
if (start < new Date()) {
errors.startDate = 'Start date must be in the future';
}
if (end <= start) {
errors.endDate = 'End date must be after start date';
}
if (maxAttendees !== undefined && maxAttendees < 1) {
errors.maxAttendees = 'Must allow at least 1 attendee';
}
if (title && title.length > 200) {
errors.title = 'Title cannot exceed 200 characters';
}
if (Object.keys(errors).length > 0) {
return res.status(422).json({
error: 'Unprocessable Entity',
message: 'Validation failed',
fields: errors
});
}
// ... create event
res.status(201).json({ id: 1, title, startDate, endDate });
});
# Flask — 422 for semantic validation errors
from flask import Flask, jsonify, request
from datetime import datetime
app = Flask(__name__)
@app.route('/api/events', methods=['POST'])
def create_event():
data = request.get_json()
errors = {}
# Semantic validation
try:
start = datetime.fromisoformat(data.get('start_date', ''))
if start < datetime.now():
errors['start_date'] = 'Start date must be in the future'
except ValueError:
errors['start_date'] = 'Invalid date format'
try:
end = datetime.fromisoformat(data.get('end_date', ''))
if 'start_date' not in errors and end <= start:
errors['end_date'] = 'End date must be after start date'
except ValueError:
errors['end_date'] = 'Invalid date format'
max_att = data.get('max_attendees')
if max_att is not None and max_att < 1:
errors['max_attendees'] = 'Must allow at least 1 attendee'
if errors:
return jsonify(
error='Unprocessable Entity',
message='Validation failed',
fields=errors
), 422
# ... create event
return jsonify(id=1, title=data['title']), 201
HTTP 400 means the request is syntactically invalid and cannot be parsed at all, such as malformed JSON. HTTP 422 means the syntax is correct but the content is semantically invalid, such as an email field containing a phone number. Use 400 for parse errors and 422 for validation errors.
HTTP 422 was originally defined in RFC 4918 as part of the WebDAV extension. While not in the original HTTP/1.1 spec, it has been widely adopted by modern APIs and frameworks. RFC 9110 (2022) includes it in the updated HTTP semantics specification.
Both approaches are valid. Use 422 if you want to distinguish between parse errors (400) and validation errors (422). Use 400 for all client errors if you prefer simplicity. The most important thing is consistency across your API.
Include a top-level error message and a fields or errors object mapping each invalid field to its specific error message. This structure allows clients to display inline validation errors next to the corresponding form fields.
Yes, many modern frameworks return 422 for validation failures. Ruby on Rails, Laravel, FastAPI, and Django REST Framework all use 422 by default. Express.js and Flask leave the choice to the developer.
Get instant alerts when your endpoints go down. 60-second checks, free forever.
Start Monitoring Free →