HTTP 400 Bad Request — What It Means and How to Fix It
400 Bad Request
Verified against RFC 9110 §15.5.1 (HTTP Semantics), MDN Web Docs: 400 Bad Request, Nginx source: ngx_http_request.c · Updated June 2026
> quick_fix
The server understood your request but rejected it because the syntax was invalid. Check the request body for malformed JSON, missing required fields, or invalid header values. Use the response body — it often names the exact field that failed validation.
# Inspect the full response body — it usually tells you exactly what's wrong
curl -i -X POST https://api.example.com/v1/users \
-H 'Content-Type: application/json' \
-d '{"email": "not-an-email", "age": -1}'
# Response body often looks like:
# {"error": "Validation failed", "fields": {"email": "invalid format", "age": "must be >= 0"}}What causes this error
HTTP 400 is a client-side error. The server received a request it could not parse or that violated its input validation rules. Common causes: malformed JSON body, Content-Type mismatch (sending JSON but declaring `text/plain`), missing required headers (e.g., `Authorization`, `Content-Length`), query parameters with invalid values, URL encoding errors, and requests that exceed size limits.
How to fix it
- 01
step 1
Read the response body carefully
Well-designed APIs return a JSON error body that names the failing field and reason. A 400 from a FastAPI or Django REST server will include a `detail` array with field-level errors. Express + Joi gives `error.details[].message`. Never skip the response body.
curl -s -X POST https://api.example.com/endpoint \ -H 'Content-Type: application/json' \ -d @payload.json | jq . - 02
step 2
Validate your JSON syntax before sending
A trailing comma, unescaped newline inside a string, or single-quoted key causes a JSON parse error that always results in a 400. Validate with `jq` or an online linter before sending.
# Validate JSON locally cat payload.json | jq . > /dev/null && echo 'valid JSON' || echo 'invalid JSON' # Or in Node node -e "JSON.parse(require('fs').readFileSync('payload.json', 'utf8'))" && echo valid - 03
step 3
Check Content-Type matches the body format
Sending a JSON body with `Content-Type: application/x-www-form-urlencoded` (or no Content-Type at all) causes most web frameworks to reject the request with a 400. Always set `Content-Type: application/json` for JSON payloads.
curl -X POST https://api.example.com/data \ -H 'Content-Type: application/json' \ -d '{"key": "value"}' - 04
step 4
Check for URL encoding issues in query parameters
Spaces, ampersands, and special characters in query params must be percent-encoded. A raw `&` inside a param value splits the parameter. Use `encodeURIComponent()` in JS or `urllib.parse.quote()` in Python.
const query = encodeURIComponent('search term & more') // 'search%20term%20%26%20more' const url = `https://api.example.com/search?q=${query}` - 05
step 5
Check size limits
Nginx default `client_max_body_size` is 1MB. Express default JSON limit is 100KB. Sending a large payload without raising these limits gives a 400 or 413. Increase the limit on the server or chunk the payload on the client.
// Express — increase JSON body limit app.use(express.json({ limit: '10mb' }))
How to verify the fix
- Response status code is no longer 400.
- Run the request through curl -i and confirm a 2xx response.
- Add request/response logging in your client to catch future validation failures early.
Why 400 happens at the runtime level
HTTP 400 is defined in RFC 9110 as the server's signal that the request was syntactically invalid or otherwise unusable. At the application server level, web frameworks run request parsing (headers, body, query string) before invoking route handlers. If parsing fails — bad JSON, invalid Content-Length, malformed multipart boundary — the framework short-circuits and emits a 400 before any application code runs. At the reverse-proxy level, Nginx, HAProxy, and AWS ALB perform HTTP protocol validation independently and can emit 400 before the request reaches the app.
Common debug mistakes for 400
- Sending a JSON body without setting `Content-Type: application/json` — the server treats it as a plain text blob and rejects it.
- Trailing comma in a JSON payload (valid in JS object literals, invalid in JSON spec).
- Using double-encoded URL parameters — encoding an already-encoded string creates `%2520` instead of `%20`.
- Missing required `Authorization` or `X-API-Key` header that the framework treats as a request validation failure rather than returning 401.
- Sending an empty body to an endpoint that requires a non-empty JSON object — many validators reject `{}` or `null` explicitly.
When 400 signals a deeper problem
Recurring 400s in production usually indicate missing client-side validation. If your frontend or SDK is sending malformed payloads, the real fix is input validation before the request leaves the client — using Zod, Yup, or Joi schemas that mirror your API contract. At the API design level, returning detailed 400 responses with field-level error messages (RFC 7807 Problem Details) dramatically reduces client debugging time and support load. Without structured error responses, every 400 becomes a guessing game for client developers.
Editor's take
The 400 error looks trivial until you're debugging a production incident at 2am where a third-party webhook is silently failing. The scenario plays out like this: an external service starts sending a new field in its JSON payload, your server-side validator rejects it with a 400 because it's using a strict schema that disallows unknown keys (`additionalProperties: false` in JSON Schema, `model_config = ConfigDict(extra='forbid')` in Pydantic v2). The webhook provider doesn't retry 4xx responses, your event queue drains to zero, and you spend an hour wondering why orders stopped flowing before checking webhook delivery logs.
The real diagnostic gap is log coverage. Most teams log the response status code but not the response body for 400s, so you see a spike in errors with no explanation. Adding response body logging — at least for 4xx and 5xx — to your reverse proxy or application middleware is the single highest-leverage change you can make here. Nginx can be configured to log `$request_body` and `$resp_body` with the `ngx_http_echo_module`. In Express, a simple error-handling middleware that serializes validation errors to JSON and logs them before responding catches the vast majority of debug sessions before they start.
The adjacent error you'll often find alongside 400 is a 431 (Request Header Fields Too Large), which surfaces when JWTs or session cookies grow beyond the proxy's header buffer size. If you're hitting 400 on authenticated endpoints that work fine in Postman but fail in your browser app, check the cookie size — browsers accumulate cookies across domains and a 400 from Nginx is often actually a 431 that wasn't properly mapped.
By Bikram Nath · Curator · Updated June 2026
Frequently asked questions
Is a 400 the server's fault or the client's fault?
Almost always the client's fault. The server is saying: 'I understood your request, but the content is invalid.' If you're building the client and getting a 400, your request payload, headers, or URL is malformed. The exception is a buggy server that sends 400 when it should send 422 or 500 — check the API docs to confirm expected behavior.
What is the difference between 400 and 422?
400 (Bad Request) means the request was syntactically malformed — the server couldn't even parse it. 422 (Unprocessable Entity) means the server parsed the request fine but the data failed semantic validation (e.g., a valid JSON body where `start_date` is after `end_date`). Many APIs use 400 for both, which is technically imprecise but common.
Why do I get a 400 with no response body?
This usually means the request was rejected at the reverse proxy (Nginx, AWS ALB, Cloudflare) before it reached your application server — often due to an oversized header, malformed URI, or HTTP protocol framing error. Check your proxy access logs, not your app logs. Nginx logs the 400 in `/var/log/nginx/error.log` with reason.