HTTP 422 Unprocessable Content
HTTP 422 Unprocessable Content is the validation error code. The server successfully parsed the request body — the JSON is syntactically valid, the Content-Type is correct — but the values fail semantic validation. A missing required field, a price below zero, a start date after the end date, an email address that fails format validation: these all produce 422. The response body must include field-level error details or the client cannot know what to fix.
Quick reference
| Code | 422 |
|---|---|
| Name | Unprocessable Content |
| Category | 4xx Client Error |
| Specification | RFC 9110 §15.5.21 |
| IANA status | Assigned |
| Cacheable? | No |
| Retryable? | Yes — after fixing the payload |
The validation error response body
A 422 without field-level details is nearly useless. The client knows the request failed but cannot determine what to fix. A well-structured 422 body follows a consistent schema that clients can parse programmatically:
HTTP/1.1 422 Unprocessable Content
Content-Type: application/json
{
"error": "validation_failed",
"message": "The request contains invalid data.",
"errors": [
{
"field": "email",
"code": "invalid_format",
"message": "Must be a valid email address."
},
{
"field": "price",
"code": "out_of_range",
"message": "Price must be greater than 0.",
"received": -5
},
{
"field": "end_date",
"code": "invalid_date_range",
"message": "End date must be after start date.",
"received": "2025-01-01",
"constraint": "start_date is 2025-06-01"
}
]
}
The field uses dot notation for nested fields (address.postal_code) and bracket notation for arrays (items[0].quantity). The code is a machine-readable string the client can switch on. The message is human-readable for display or logging.
Common validation scenarios
Required field missing or empty:
{"name": "", "email": null}
// 422: name cannot be blank, email is required
Format validation: Email addresses, phone numbers, URLs, postal codes, credit card numbers, UUID strings. The value is present but does not match the required format.
Range validation: Numeric values below minimum or above maximum. Strings shorter than minimum length or longer than maximum. Dates outside the allowed window.
Business rule validation: Start date must be before end date. Quantity must be a whole number. Discount percentage cannot exceed 100. Password must contain at least one uppercase letter and one number.
Relationship validation: A referenced entity ID does not exist (e.g., category_id: 999 where category 999 does not exist). This is distinct from a 404 — the request itself is the issue, not the target URL.
Server-side implementation
Express.js with express-validator:
const { body, validationResult } = require('express-validator');
const validateProduct = [
body('name').notEmpty().withMessage('Name is required'),
body('price').isFloat({min: 0.01}).withMessage('Price must be positive'),
body('email').isEmail().withMessage('Must be a valid email'),
];
app.post('/api/products', validateProduct, (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(422).json({
error: 'validation_failed',
errors: errors.array().map(e => ({
field: e.path,
message: e.msg
}))
});
}
// create product...
});
Django REST Framework: DRF serializers return 422 (via 400 in older DRF, configurable) automatically with field-level errors when validation fails:
class ProductSerializer(serializers.ModelSerializer):
def validate_price(self, value):
if value <= 0:
raise serializers.ValidationError("Price must be positive.")
return value
def validate(self, data):
if data["end_date"] <= data["start_date"]:
raise serializers.ValidationError(
{"end_date": "End date must be after start date."}
)
return data
FastAPI: FastAPI uses Pydantic models for automatic validation and returns structured 422 errors by default:
from pydantic import BaseModel, validator, EmailStr
class ProductCreate(BaseModel):
name: str
price: float
email: EmailStr
@validator('price')
def price_positive(cls, v):
if v <= 0:
raise ValueError('Price must be positive')
return v
# FastAPI automatically returns 422 with Pydantic validation errors
422 vs 400 vs 409
| Code | Meaning | Example |
|---|---|---|
| 422 | Validation failed | Valid JSON, but email format is wrong |
| 400 | Parse or syntax error | Malformed JSON (missing closing brace), wrong Content-Type |
| 409 | Conflict with existing state | Email is valid format but already registered in the system |
Frequently asked questions
What is the difference between 400 and 422?
400 is for request parsing failures: malformed JSON, wrong Content-Type, unparseable syntax. 422 is for semantic validation failures: the request was parsed successfully but the values are invalid according to the server’s rules. A simple heuristic: if the JSON parser threw an exception, it is 400. If the validator threw an exception after successful parsing, it is 422.
Should my API use 400 or 422 for validation errors?
Either is used in practice. The argument for 422: it is semantically precise (parsing succeeded, validation failed). The argument for 400: it is universally understood and older APIs do not distinguish the two. If you choose 400 for validation errors, include the same structured error body as 422 so clients know what to fix.
How should a client handle 422?
Do not auto-retry. Parse the error response body, map the field errors to the corresponding form fields or request parameters, and surface the error messages to the user or operator. Fix the payload based on the error details and resubmit.
Is 422 retryable?
Yes, after fixing the payload. Unlike 500 (server error) or 503 (temporarily unavailable), the issue is with the client’s data, not the server’s state. Correct the failing fields and retry with the corrected data.
Related guides
HTTP 400 Bad Request · HTTP 409 Conflict · HTTP 415 Unsupported Media Type
Comparisons
Standards reference
Definitions from the IANA HTTP Status Code Registry and RFC 9110 §15.5.21. Human-readable guidance by ErrorLookup. · HTTP 422 quick reference →