encode/httpx · error · HTTPStatusError

{error_type} '{0.status_code} {0.reason_phrase}' for url '{0

Error message

{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'
Redirect location: '{0.headers[location]}'
For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}

What it means

The message format used by `raise_for_status` when the response has a redirect location (3xx with a `Location` header, as defined by `has_redirect_location`). It raises `HTTPStatusError` with the status code, reason, URL, and the redirect target, linking the MDN status reference. This is the 'redirect' variant of the two message branches in `raise_for_status`.

Source

Thrown at httpx/_models.py:829

                "Redirect location: '{0.headers[location]}'\n"
                "For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}"
            )
        else:
            message = (
                "{error_type} '{0.status_code} {0.reason_phrase}' for url '{0.url}'\n"
                "For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/{0.status_code}"
            )

        status_class = self.status_code // 100
        error_types = {
            1: "Informational response",
            3: "Redirect response",
            4: "Client error",
            5: "Server error",
        }
        error_type = error_types.get(status_class, "Invalid status code")
        message = message.format(self, error_type=error_type)
        raise HTTPStatusError(message, request=request, response=self)

    def json(self, **kwargs: typing.Any) -> typing.Any:
        return jsonlib.loads(self.content, **kwargs)

    @property
    def cookies(self) -> Cookies:
        if not hasattr(self, "_cookies"):
            self._cookies = Cookies()
            self._cookies.extract_cookies(self)
        return self._cookies

    @property
    def links(self) -> dict[str | None, dict[str, str]]:
        """
        Returns the parsed header links of the response, if any
        """
        header = self.headers.get("link")
        if header is None:

View on GitHub (pinned to b5addb64f0)

Solutions

  1. Enable `follow_redirects=True` on the client or per-request if you want httpx to follow automatically.
  2. Inspect `response.headers['Location']` and `response.is_redirect` before calling `raise_for_status()` to handle redirects yourself.
  3. Wrap in `try/except httpx.HTTPStatusError as e:` and read `e.response.status_code` / `e.response.headers['Location']`.
  4. Verify the redirect target is the expected host to avoid open-redirect surprises.

Example fix

// before
r = client.get(url, follow_redirects=False)
r.raise_for_status()  # HTTPStatusError on 302

// after
r = client.get(url, follow_redirects=True)
r.raise_for_status()
Defensive patterns

Strategy: try-catch

Validate before calling

if response.is_redirect and not response.is_success:
    # handle redirect manually instead of raise_for_status()
    loc = response.headers.get('Location')

Type guard

import httpx

def is_unfollowed_redirect(resp: httpx.Response) -> bool:
    return resp.has_redirect_location and not resp.is_success

Try / catch

try:
    response.raise_for_status()
except httpx.HTTPStatusError as e:
    if e.response.has_redirect_location:
        next_url = e.response.headers['Location']
        # follow manually or surface to caller
    else:
        raise

Prevention

When it happens

Trigger: Calling `response.raise_for_status()` on a 301/302/303/307/308 response whose `Location` header is present and `is_success` is False — typically because redirects were disabled (`follow_redirects=False`) or the redirect chain was halted.

Common situations: Passing `follow_redirects=False` to inspect a redirect manually; SSO/OAuth flows where you intentionally stop at the 302; debugging a redirect loop.

Related errors


AI-assisted analysis of encode/httpx@b5addb64f0 (2026-08-04). Data as JSON: /data/errors/75dd8c8b76a71165.json. Report an issue: GitHub.