psf/requests · error · HTTPError

{self.status_code} Client Error: {reason} for url: {self.url

Error message

{self.status_code} Client Error: {reason} for url: {self.url}

What it means

Raised by Response.raise_for_status() in src/requests/models.py when the response status code is in the 4xx range (400 <= status_code < 500). Requests wraps the status, the server-supplied reason phrase (decoded utf-8, falling back to iso-8859-1), and the final URL into a requests.exceptions.HTTPError so callers can turn HTTP-level failures into Python exceptions instead of silently inspecting status codes.

Source

Thrown at src/requests/models.py:1171

            try:
                reason = self.reason.decode("utf-8")
            except UnicodeDecodeError:
                reason = self.reason.decode("iso-8859-1")
        else:
            reason = self.reason

        if 400 <= self.status_code < 500:
            http_error_msg = (
                f"{self.status_code} Client Error: {reason} for url: {self.url}"
            )

        elif 500 <= self.status_code < 600:
            http_error_msg = (
                f"{self.status_code} Server Error: {reason} for url: {self.url}"
            )

        if http_error_msg:
            raise HTTPError(http_error_msg, response=self)

    def close(self) -> None:
        """Releases the connection back to the pool. Once this method has been
        called the underlying ``raw`` object must not be accessed again.

        *Note: Should not normally need to be called explicitly.*
        """
        if not self._content_consumed:
            self.raw.close()

        release_conn = getattr(self.raw, "release_conn", None)
        if release_conn is not None:
            release_conn()

View on GitHub ↗ (pinned to 414f0513c3)

Solutions

  1. Inspect the failing response before raising: check response.status_code and response.text/response.json() for the server's error detail.
  2. Fix the request per status: refresh/attach credentials for 401/403, correct the URL or resource ID for 404, validate the payload for 400/422.
  3. For 429, honor the Retry-After header or add backoff (e.g. urllib3 Retry with status_forcelist).
  4. Wrap the call in try/except requests.exceptions.HTTPError and use err.response for structured handling.

Example fix

# before
resp = requests.get(url)
resp.raise_for_status()
data = resp.json()

# after
resp = requests.get(url, headers={"Authorization": f"Bearer {token}"})
try:
    resp.raise_for_status()
except requests.HTTPError as err:
    print(err.response.status_code, err.response.text)
    raise
data = resp.json()
Defensive patterns

Strategy: try-catch

Type guard

def is_client_error(resp):
    return 400 <= resp.status_code < 500

Try / catch

try:
    resp = requests.get(url)
    resp.raise_for_status()
except requests.exceptions.HTTPError as e:
    if e.response is not None and 400 <= e.response.status_code < 500:
        handle_client_error(e.response)

Prevention

When it happens

Trigger: Calling response.raise_for_status() after any requests.get/post/etc. that returned 400 Bad Request, 401 Unauthorized, 403 Forbidden, 404 Not Found, 429 Too Many Requests, or any other 4xx. It is never raised automatically — only when raise_for_status() (or code that calls it, e.g. many API client wrappers) is invoked.

Common situations: Missing or expired API keys/tokens (401/403), wrong endpoint paths or IDs (404), malformed JSON bodies or missing required parameters (400/422), rate limiting (429), and redirects landing on auth walls. The URL in the message reflects the final URL after redirects, which often reveals the real problem.

Related errors


AI-assisted analysis of psf/requests@414f0513c3 (2026-07-31). Data as JSON: /data/errors/28027f65d3a4d5fe.json. Report an issue: GitHub ↗.