HTTP 400 Bad Request: What It Means and How to Fix It
HTTP 400 Bad Request is a client error status code meaning the server cannot or will not process a request because of something it sees as a client mistake, such as malformed syntax, invalid message framing, or a bad parameter. RFC 9110 defines it as the generic 4xx response, so the body usually says what went wrong.
- Code
- 400
- Name
- Bad Request
- Class
- 4xx client error
- Retry?
- No, fix the cause first
What causes a 400 error?
- →A malformed URL: unencoded spaces, stray quotes, or a query string built by string concatenation.
- →A JSON or form body that does not match the declared
Content-Type, or is not valid JSON at all. - →Oversized or corrupted cookies and headers. Some servers answer these with 400 rather than 431.
- →Missing or invalid required parameters on an API endpoint.
How do you fix a 400 error when web scraping?
- →Build URLs with a URL library (
urllib.parse,new URL()) instead of string concatenation, and percent-encode query values. - →Log the exact request your client sent and compare it with what a browser sends in DevTools. A missing header or a double-encoded value is usually visible side by side.
- →Clear the cookie jar between sessions. A cookie that grew across thousands of requests can push headers past the server limit.
- →Do not retry a 400 unchanged. The same request will fail the same way.
How do you fix a 400 error on your own server?
- →Return a body that names the invalid field and the expected format, so clients can fix the request without guessing.
- →Use 422 for requests that parse correctly but fail validation, and keep 400 for requests that cannot be parsed.
- →Check your reverse proxy limits for header and cookie size if 400s cluster on long-lived sessions.
How do you handle a 400 error in a retry loop?
400 is not in RETRYABLE, so raise_for_status() raises on the first response instead of spending retries on a request that will fail the same way. Fix the cause, then send the request again.
import random
import time
import requests
RETRYABLE = {408, 429, 500, 502, 503, 504, 520, 521, 522, 523, 524}
def fetch(url: str, max_attempts: int = 5) -> requests.Response:
for attempt in range(max_attempts):
try:
response = requests.get(url, timeout=(10, 60))
except requests.Timeout:
time.sleep(2**attempt + random.uniform(0, 1))
continue
if response.status_code not in RETRYABLE:
response.raise_for_status()
return response
retry_after = response.headers.get("Retry-After", "")
backoff = 2**attempt + random.uniform(0, 1)
time.sleep(min(int(retry_after) if retry_after.isdigit() else backoff, 60))
raise RuntimeError(f"Gave up on {url} after {max_attempts} attempts")
How does Context.dev handle a 400 error?
The Context.dev API returns 400 for invalid requests: unknown or conflicting options, a non-public URL, or waits longer than the timeout, with a message that names the problem. When a target site rejects the fetch, the failed output carries an error_code and message inside an HTTP 200 response, and a request where every output fails is not charged.
See what the web scraping API does on every request, or read how to fix HTTP errors in web scraping for a longer walkthrough.
Frequently asked questions about a 400 error
Is a 400 error my fault or the server’s?
The server is saying the request is at fault. Usually that is accurate: a malformed URL, body, or header. Occasionally a server returns 400 for its own reasons, so read the response body before rewriting your client.
Should I retry a 400 Bad Request?
No. A 400 describes the request, not the server state, so sending it again unchanged returns the same error. Fix the request first.
What is the difference between 400 and 422?
A 400 means the server could not parse or accept the request at all. A 422 means it parsed the request fine but the content failed validation.
Which status codes are related to 400?
Sources
Last reviewed