Introducing Highlights: the context that matters

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

Ship an agent that actually knows things.

Free tier, 10-minute integration, and the same API powering agents at Mintlify, daily.dev, and Propane. No credit card to start.