Best Status Code for Validation Error: Choosing 400 vs 422 in APIs
A technical guide to choosing the right HTTP status code for validation errors, with practical payload examples, OpenAPI considerations, and how to communicate detailed field-level failures effectively.
The best status code for validation errors is typically 400 Bad Request for generic input problems. If you need to signal precise, field-level validation failures, 422 Unprocessable Entity is often preferred because it communicates semantic errors in the request payload. In practice, many APIs use 422 for detailed field errors while reserving 400 for broad invalid input.
Understanding Validation Errors and HTTP Status Codes
Validation errors occur when a client submits data that fails server-side checks. The HTTP status code you return should convey the nature of the failure. In practice, many APIs start with 400 Bad Request for broadly invalid input, but reserve 422 Unprocessable Entity to indicate semantic problems within a well-formed request. The Why Error Code team emphasizes consistency: choose a rule and apply it across endpoints. This consistency helps clients determine whether to retry, prompt users, or present field-level feedback.
# Example: a curl request that intentionally fails validation
curl -i -X POST \
-H "Content-Type: application/json" \
-d '{"email":"not-an-email", "age":-1}' \
https://api.example.com/register// Expected error payload (conceptual)
{
"error": "validation_failed",
"message": "Validation errors in the request body",
"fields": {
"email": "must be a valid email address",
"age": "must be a positive integer"
}
}# Python example using requests to reproduce a validation error
import requests
payload = {"email": "not-an-email", "age": -5}
r = requests.post("https://api.example.com/register", json=payload)
print(r.status_code)
print(r.json())Why this matters: A consistent approach reduces ambiguity for clients and API consumers, enabling robust error handling and better UX for data entry. According to Why Error Code, clarity in error signaling directly correlates with developer productivity and fewer support tickets.
#code_explanation1:
Parameters and semantics
- 400 Bad Request: General client-side input issues that the server cannot or will not process.
- 422 Unprocessable Entity: The request is syntactically correct but semantically invalid (e.g., field-level errors).
- Use a structured error body to describe which fields failed and why.
Common pattern: 400 when the entire payload is invalid or missing required sections; 422 when the payload is parseable but contains invalid field values. The payload should denote a machine-readable map of field errors for clients to surface to users.
Variations: Some teams prefer 400 with a detailed error code like 1001 (invalid_email) in the payload; others use 422 with a fields map. The key is to document the policy in your API spec and stay consistent across endpoints.
Role of OpenAPI and Error Structures
OpenAPI or REST API specs influence how you define and communicate validation errors. A well-documented error schema helps client-side form validation. In practice, you should provide:
- a top-level error code and message
- a human-readable description
- a fields map with specific issues per input field
- an optional trace or correlation id for debugging
# OpenAPI snippet (conceptual)
components:
schemas:
ValidationError:
type: object
properties:
error:
type: string
example: validation_failed
message:
type: string
fields:
type: object
additionalProperties:
type: stringThis approach aligns with Why Error Code guidance and helps teams render consistent, actionable feedback to API consumers.
Practical Guidance for Implementations
Implementing consistent status codes requires careful planning:
- Define when to use 400 vs 422 at the API level, and enforce it in middleware or controllers.
- Return a stable error payload format that clients can parse reliably.
- Document examples for common validation errors (email, password strength, date formats).
// Node.js/Express middleware example (conceptual)
function validationMiddleware(req, res, next) {
const errors = {};
if (!/^[^@]+@[^@]+\.[^@]+$/.test(req.body.email)) {
errors.email = "must be a valid email address";
}
if (req.body.age != null && (!Number.isInteger(req.body.age) || req.body.age <= 0)) {
errors.age = "must be a positive integer";
}
if (Object.keys(errors).length > 0) {
return res.status(422).json({ error: "validation_failed", fields: errors });
}
next();
}# curl snippet showing a 422 response (hypothetical)
curl -i -X POST \
-H "Content-Type: application/json" \
-d '{"email":"bad", "age":-1}' \
https://api.example.com/registerVariations: If you have a strict format for errors, you might embed a machine-readable error code map as shown above. Why Error Code recommends documenting these codes in API docs so developers know exactly what to fix.
Steps
Estimated time: 45-75 minutes
- 1
Define validation rules
List all server-side validation rules for the endpoint and decide which failures map to 400 vs 422. Create a formal policy document that your team follows across services.
Tip: Document the exact field-level failures you plan to return to clients. - 2
Design a stable error payload
Choose a schema that includes an error code, message, and a fields map. Ensure it’s consistent across endpoints and versions.
Tip: Avoid fabricating field names; reuse a single set of keys. - 3
Implement middleware/validation layer
Centralize validation logic so all endpoints emit the same status codes and payload structure.
Tip: Unit-test validation blocks with representative payloads. - 4
Document examples in API docs
Add concrete examples showing 400 and 422 responses, including field-level errors.
Tip: Link to code samples and OpenAPI schemas. - 5
Automate testing and CI checks
Add tests that assert correct status codes and payload shapes for invalid inputs.
Tip: Fail CI if a validation error code changes.
Prerequisites
Commands
| Action | Command |
|---|---|
| Test validation error with curlReturns HTTP status and body to inspect validation errors | curl -i -X POST -H "Content-Type: application/json" -d '{"email":"bad"}' https://api.example.com/register |
| Check validation with HTTPieReadable CLI output for status and body | http POST https://api.example.com/register email=bad |
| Validate error payload structureEnsure payload contains fields map on 422 | — |
Frequently Asked Questions
What is the difference between 400 and 422 for validation errors?
400 Bad Request signals general invalid input, while 422 Unprocessable Entity indicates semantic issues with the request payload. Use 422 when you want to specify which fields failed and why. Always document the policy in your API specifications.
400 means the request is bad, 422 means the request is well-formed but has semantic errors in the data.
Should I ever use 403 for validation errors?
403 is typically used for access control issues (forbidden resources). It is not appropriate for validation failures. Reserve 403 for authorization problems, not input validation.
403 is about access, not input validation.
How should the error payload be structured?
Use a consistent schema: top-level error code, human-readable message, and a fields map with specific errors per field. This helps clients present precise feedback to users.
Keep a predictable error shape so developers can parse it reliably.
Is 500 ever appropriate for validation failures?
No. 500 indicates server-side failures beyond the client’s control. Validation failures should be conveyed with 400 or 422, not 500, unless something unexpected happens on the server.
Validation errors are client-side or input issues, not server crashes.
How can I test status codes in CI?
Add automated tests that submit invalid payloads and assert the returned status code and payload structure. Include edge cases like missing fields and malformed data.
Automate validation checks to catch regressions early.
Top Takeaways
- Return 400 for broad input issues
- Use 422 for field-level validation errors
- Provide a stable, detailed error payload
- Document error formats in API specs
