# 422 Unprocessable Entity (Unprocessable Content) Explained

> 422 Unprocessable Content (formerly Unprocessable Entity): the JSON parsed but failed validation. FastAPI, Laravel and Rails errors, 422 vs 400, RFC 9457.

Source: https://howhttpworks.com/status-codes/422
Last reviewed: 2026-10-05

> **TL;DR:** 422 Unprocessable Content means the server rejected the data in your request, usually because a field failed validation. Read the response body, fix the named field or missing CSRF token, then resend the corrected request.

## What you see in your client

The messages and response bodies here are illustrative. Client messages use `https://api.example.com/resource`; reason phrases and URLs can vary.

- **Axios:** `AxiosError: Request failed with status code 422` with the default status handling. Inspect `error.response.data` for the server's explanation. [Axios source](https://github.com/axios/axios/blob/v1.x/lib/core/settle.js).
- **fetch:** the promise resolves with `response.status === 422` and `response.ok === false`. Check the status before reading the body; HTTP errors do not enter `catch` automatically. [MDN fetch](https://developer.mozilla.org/en-US/docs/Web/API/Window/fetch).
- **Python requests:** `requests.exceptions.HTTPError: 422 Client Error: Unprocessable Entity for url: https://api.example.com/resource` when you call `response.raise_for_status()`. [Requests source](https://requests.readthedocs.io/en/latest/_modules/requests/models/#Response.raise_for_status).
- **curl -f:** `curl: (22) The requested URL returned error: 422`. Use `curl -i` without `-f` while inspecting the error body. [curl source](https://github.com/curl/curl/blob/master/lib/http.c).
- **.NET:** `HttpRequestException` with the English message `Response status code does not indicate success: 422 (Unprocessable Entity).` after `EnsureSuccessStatusCode()`. [Runtime message template](https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Http/src/Resources/Strings.resx).
- **Spring WebClient:** `WebClientResponseException$UnprocessableEntity: 422 Unprocessable Entity from POST https://api.example.com/resource` with `retrieve()`. Read `getResponseBodyAsString()`. [Spring 6.2 source](https://github.com/spring-projects/spring-framework/blob/6.2.x/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientResponseException.java).
- **Spring RestTemplate (6.2):** `HttpClientErrorException$UnprocessableEntity: 422 Unprocessable Entity on POST request for "https://api.example.com/resource": [no body]` with the default error handler and an empty response body. Read the exception body when present. [Message builder](https://github.com/spring-projects/spring-framework/blob/6.2.x/spring-web/src/main/java/org/springframework/web/client/DefaultResponseErrorHandler.java), [exception subclasses](https://docs.spring.io/spring-framework/docs/6.2.x/javadoc-api/org/springframework/web/client/HttpClientErrorException.html).

Spring 7 adds `WebClientResponseException$UnprocessableContent: 422 Unprocessable Content from POST https://api.example.com/resource`. Both names identify status 422. [Spring source](https://github.com/spring-projects/spring-framework/blob/main/spring-webflux/src/main/java/org/springframework/web/reactive/function/client/WebClientResponseException.java).

## What it means

RFC 9110 section 15.5.21 defines 422 as: the server understands the content type and the syntax is correct, but it was unable to process the contained instructions. Typical triggers are a missing required field, a wrong data type, an email that fails a format rule, or a business rule such as an end date before the start date. The name was "Unprocessable Entity" in WebDAV (RFC 4918); RFC 9110 renamed it "Unprocessable Content". Libraries and older docs still use either phrase.

```http
HTTP/1.1 422 Unprocessable Content
Content-Type: application/problem+json

{
  "type": "https://example.com/problems/validation-error",
  "title": "Your request parameters didn't validate.",
  "status": 422,
  "errors": [
    { "field": "email", "message": "must be a valid email address" },
    { "field": "age", "message": "must be at least 18" }
  ]
}
```

## What you actually see in each framework

FastAPI (Pydantic v2) answers with a `detail` array where `loc` is the path to the bad field:

```json
{
  "detail": [
    { "type": "missing", "loc": ["body", "email"], "msg": "Field required", "input": {} }
  ]
}
```

Laravel validation produces this JSON shape when the request expects JSON; a traditional form submission redirects back with session errors:

```json
{
  "message": "The email field is required.",
  "errors": { "email": ["The email field is required."] }
}
```

Rails maps `ActionController::InvalidAuthenticityToken` ("Can't verify CSRF token authenticity") to 422. For form re-renders with errors, controllers choose the status: Turbo displays failed submissions when the response uses a 4xx status such as 422. The GitHub REST API returns 422 with `"message": "Validation Failed"` and an `errors` array of `resource`, `field` and `code`. Spring MVC request validation and Django REST framework serializer validation default to 400; inspect their error bodies for the failing fields. [Spring mappings](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/servlet/mvc/support/DefaultHandlerExceptionResolver.html), [DRF exceptions](https://www.django-rest-framework.org/api-guide/exceptions/).

## Common causes by stack

- **FastAPI / Pydantic:** missing fields or invalid path, query and body values produce 422 with a `detail` array; `loc` starts with the input location. Fix the named input, not just the JSON body. [FastAPI validation](https://fastapi.tiangolo.com/tutorial/handling-errors/).
- **Laravel:** `$request->validate(...)` throws `ValidationException` on failure; requests expecting JSON receive 422 with `message` and `errors`. Send `Accept: application/json` for an API workflow and fix the rule named in the error. CSRF token mismatches map to [419](https://howhttpworks.com/status-codes/419). [Validation docs](https://laravel.com/docs/12.x/validation), [exception handler](https://github.com/laravel/framework/blob/12.x/src/Illuminate/Foundation/Exceptions/Handler.php).
- **Rails:** `ActionController::InvalidAuthenticityToken` maps to 422; restore the CSRF token and matching session cookie. For model validation, the controller chooses the response status; render the failed form with 422 for Turbo. [Rails mappings](https://github.com/rails/rails/blob/main/actionpack/lib/action_dispatch/middleware/exception_wrapper.rb), [Turbo form handling](https://turbo.hotwired.dev/handbook/drive#redirecting-after-a-form-submission).
- **Express:** the `safeParse` handler below explicitly selects 422 for schema errors; fix the reported `field` path. Express's JSON parser uses 400 for malformed JSON, so keep parser failures separate from this handler. [Express parser errors](https://expressjs.com/en/resources/middleware/body-parser/#errors).

FastAPI also returns 422 for malformed JSON: its router wraps `JSONDecodeError` in `RequestValidationError`, with `type: "json_invalid"` and `msg: "JSON decode error"`. That is a framework convention beyond the RFC's semantic-validation definition. Fix the JSON syntax before looking for a field rule. [FastAPI router source](https://github.com/fastapi/fastapi/blob/master/fastapi/routing.py).

## Fix it as the client

1. Read the response body; do not stop at the status line. The field name is in `loc`, `errors` or `details`.
2. Compare field types to the API schema: `"qty": "2"` vs `2`, dates in ISO 8601 (`2027-01-31T10:00:00Z`), enums with the exact casing.
3. Check for required fields you sent as `null` or an empty string.
4. For Rails CSRF 422s, send the token in `X-CSRF-Token` and make sure the session cookie is attached (see [cookie not sent](https://howhttpworks.com/debug/cookie-not-sent)).

Reproduce with curl so you can see the raw body:

```bash
curl -sv -X POST https://api.example.com/users \
  -H 'Content-Type: application/json' \
  -d '{"email":"not-an-email","age":-5}'
```

## Handling 422 in a workflow

With FastAPI, `loc: ["query", "limit"]` points to the URL query, while `["body", "items", 0, "quantity"]` points to the first item in a JSON array. Laravel uses dotted error keys for nested fields, such as `users.0.email`. Match these paths to form fields so users can correct the rejected value. Keep the submitted data available while displaying the error; a status-only alert loses the field information the server already supplied. [FastAPI errors](https://fastapi.tiangolo.com/tutorial/handling-errors/), [Laravel response format](https://laravel.com/docs/12.x/validation#validation-error-response-format).

A workflow retry should use corrected input. For a validation failure, route the error body to the step that collects or transforms the data. For a Rails CSRF failure, refresh the page's token and session before submitting again. If the response contains `json_invalid`, validate the serialized bytes instead of changing field values. These are different repairs for the same status number; choose from the response body and the framework that sent it.

## Build it as the API author

Return all validation errors at once, in one machine-readable shape. RFC 9457 `application/problem+json` is the standard container:

```javascript
// Express with a schema validator such as zod
app.post('/users', (req, res) => {
  const result = userSchema.safeParse(req.body)
  if (!result.success) {
    return res.status(422).type('application/problem+json').json({
      type: 'https://example.com/problems/validation-error',
      title: "Your request parameters didn't validate.",
      status: 422,
      errors: result.error.issues.map((i) => ({ field: i.path.join('.'), message: i.message }))
    })
  }
  res.status(201).json(createUser(result.data))
})
```

Keep secrets out of error output. Use [409 Conflict](https://howhttpworks.com/status-codes/409) when valid data conflicts with current server state; reserve 422 for instructions the API rejects as invalid.

## 422 versus neighbors

- [400 Bad Request](https://howhttpworks.com/status-codes/400): broken syntax, unparseable JSON, or a framework that does not distinguish validation.
- [409 Conflict](https://howhttpworks.com/status-codes/409): the data is valid but clashes with current server state.
- [415 Unsupported Media Type](https://howhttpworks.com/status-codes/415): the body format itself is not accepted.
- [413 Content Too Large](https://howhttpworks.com/status-codes/413) (formerly Payload Too Large): the body is over a size limit.

The [request builder](https://howhttpworks.com/tools/playground) lets you send an invalid body and inspect the response.

See also the comparison [400 vs 422](https://howhttpworks.com/compare/400-vs-422): which of the two to return for validation errors and what common frameworks default to.
