400 Bad Request in REST APIs: Causes, Fixes and How to Prevent It
A 400 Bad Request response means the server will not process the request because it sees something wrong on the client side, such as malformed syntax or invalid framing. RFC 9110 section 15.5.1 defines it this way. In a REST API, the usual causes are invalid JSON, a wrong Content-Type, missing or wrongly typed fields, bad query parameters, or headers and cookies that are too large. Resending the same request unchanged will fail again: fix the request, not the server.
This guide explains what status code 400 means, how it differs from 401, 403, 404, 409, 415 and 422, what a good error body looks like, and a checklist for debugging an API 400 response.
What response code 400 means
HTTP status codes in the 4xx range mean "the client did something wrong." 400 is the generic one. RFC 9110 says a server uses it when it "cannot or will not process the request due to something that is perceived to be a client error." So 400 is the fallback when no more specific 4xx code fits.
Two practical points follow:
- For API clients: a 400 is not retryable as-is. Read the response body, change the request, then send it again.
- For API designers: use a more specific code when one exists, and always say what was wrong in the body.
400 vs other 4xx status codes
| Code | Meaning (RFC 9110) | Use it when |
|---|---|---|
| 400 Bad Request | Generic client error | Malformed JSON, unparseable query string, invalid framing |
| 401 Unauthorized | Missing or invalid authentication | No token, expired token, wrong API key |
| 403 Forbidden | Authenticated but not allowed | Valid user without permission for this resource |
| 404 Not Found | Resource does not exist (or is hidden) | Wrong ID or path |
| 409 Conflict | Conflicts with the resource's current state | Duplicate unique value, version mismatch |
| 413 Content Too Large | Body exceeds the server limit | Upload or JSON body too big |
| 415 Unsupported Media Type | Body format not accepted | Content-Type: text/plain sent to a JSON endpoint |
| 422 Unprocessable Content | Syntax is fine, content is invalid | Valid JSON, but email is not an email address |
| 431 Request Header Fields Too Large | Headers too big (RFC 6585) | Oversized cookies or tokens |
The 400 vs 422 line is where APIs differ most. A common convention: return 400 when the request can't be parsed, and 422 when it parses but fails validation. Stripe, for example, returns 400 for invalid parameters, while FastAPI defaults to 422. Either is acceptable if you are consistent and document it. Some servers also send 400 where 413 or 431 would be more precise, so a 400 with a huge cookie header is still worth suspecting.
Common causes of a 400 Bad Request in REST APIs
- Malformed JSON. A trailing comma, single quotes, an unescaped quote inside a string, or a body cut off by the shell.
{"name": "Ada",}is invalid JSON. - Wrong or missing
Content-Type. You send JSON but the header saysapplication/x-www-form-urlencoded, or there is no header, so the server can't parse the body. Some servers answer 415, many answer 400. - Missing required fields or wrong types.
"quantity": "3"(a string) where the schema expects an integer, or a date in the wrong format. - Bad query parameters. Unknown filter names, invalid values (
?limit=abc), or characters that weren't URL-encoded (spaces,&,#,+in a value). - Encoding problems. A body that isn't valid UTF-8, a double-encoded URL (
%2520), or a byte-order mark at the start of a JSON file. - Headers or cookies too large. A browser with years of cookies for a domain, or a giant JWT, can exceed the server's header limit. Many servers and proxies answer with "400 Bad Request: Request Header Or Cookie Too Large" instead of 431.
- Invalid framing. A
Content-Lengththat doesn't match the body, or a body sent withGETto a server that rejects it.
How to fix a 400 error: debugging checklist
Work through these in order. Most API 400s are found in the first three steps.
- Read the response body. Good APIs say exactly which field failed. Print it in full:
curl -ishows headers and body. - Validate the JSON. Paste the exact bytes you sent into a JSON validator, or run
echo "$BODY" | jq .. Log the serialized body, not the object you think you serialized. - Check
Content-TypeandAccept. For JSON, sendContent-Type: application/json. - Compare against the API docs. Check required fields, types, enum values, date formats and the URL path version (
/v1vs/v2). - Reproduce with a minimal request. Strip optional fields until it works, then add them back one at a time. A tool like Postman (or one of its free alternatives) makes this quick.
- Check encoding. URL-encode query values (
encodeURIComponentin JavaScript) and make sure nothing is encoded twice. - Check header size. In a browser, clear the site's cookies or try a private window. In code, look for duplicated or oversized
AuthorizationandCookieheaders. - Look at proxies in between. A CDN, load balancer or API gateway can reject a request before your app sees it. If your app logs nothing, the 400 came from upstream.
A quick curl test that rules out client libraries:
curl -i -X POST https://api.example.com/v1/orders \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{"product_id": 42, "quantity": 3}'
Fixing 400 Bad Request in Chrome (as a user)
If you see "400 Bad Request" on a normal website rather than in your own API, it is usually a corrupted or oversized cookie or a mistyped URL. Check the URL, then clear cookies for that site (in Chrome: the site settings icon in the address bar, then Cookies and site data). Try a private window to confirm.
Return useful 400 responses: RFC 9457 problem details
RFC 9457 (which replaced RFC 7807) defines a standard JSON error format with the media type application/problem+json. Its members are type, title, status, detail and instance, and you can add your own fields, such as a list of field errors:
HTTP/1.1 400 Bad Request
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/validation-error",
"title": "Your request is not valid.",
"status": 400,
"detail": "2 fields failed validation.",
"instance": "/v1/orders",
"errors": [
{ "pointer": "/quantity", "detail": "must be an integer greater than 0" },
{ "pointer": "/product_id", "detail": "is required" }
]
}
Clients can handle every error the same way, and humans can read detail without opening your source code.
Prevent 400s: validate on the server
Validate every request at the edge of your API, before business logic runs, and turn failures into a clear 400 (or 422). Here is an Express example with Zod:
import express from 'express';
import { z } from 'zod';
const app = express();
app.use(express.json({ limit: '100kb' }));
const Order = z.object({
product_id: z.number().int().positive(),
quantity: z.number().int().min(1).max(100),
});
app.post('/v1/orders', (req, res) => {
const result = Order.safeParse(req.body);
if (!result.success) {
return res.status(400).type('application/problem+json').json({
type: 'https://api.example.com/problems/validation-error',
title: 'Your request is not valid.',
status: 400,
errors: result.error.issues.map((i) => ({
pointer: '/' + i.path.join('/'),
detail: i.message,
})),
});
}
res.status(201).json({ id: 1, ...result.data });
});
// Malformed JSON: express.json() throws before the route runs
app.use((err, req, res, next) => {
if (err.type === 'entity.parse.failed') {
return res.status(400).type('application/problem+json').json({
title: 'Malformed JSON body.',
status: 400,
});
}
next(err);
});
app.listen(3000);
On the client side, prevention is mostly discipline:
- Build bodies with
JSON.stringify(or your language's serializer), never by joining strings. - Build query strings with
URLSearchParamsinstead of concatenating them. - Generate types or clients from the API's OpenAPI spec so wrong field names fail at compile time.
- Log the outgoing request (minus secrets) when a call fails, so you can see what was really sent.
If you are building against third-party APIs, our list of free APIs for developers and the API tools directory cover testing and mocking tools that help catch these issues early.
FAQ
What is response code 400?
It is the HTTP status for "Bad Request": the server rejected the request because of a client-side problem such as invalid syntax, an unreadable body or bad parameters. The fix is to change the request. Retrying it unchanged will fail again.
Is a 400 error my fault or the server's?
By definition the server thinks the client made the mistake. Sometimes the server is wrong, for example when a proxy rejects valid headers or when an API's docs are out of date. Still, start debugging on the client.
Should an API return 400 or 422 for validation errors?
Both are used. A common split is 400 for requests that can't be parsed and 422 for well-formed requests that fail validation rules. Pick one convention, document it, and put field-level details in the body.
How do I fix "400 Bad Request: Request Header Or Cookie Too Large"?
Clear the cookies for that site, or try a private window. If it's your own app, shrink the cookies or tokens you set, or raise the header size limit on your web server or proxy.
Why does my request work in Postman but return 400 in code?
Usually the code sends something different: a missing Content-Type, an object that was never serialized, a double-encoded URL, or extra headers added by a library. Log the raw outgoing request and compare it with Postman's.