Skip to content

fix(response): keep status code and url when an error body is not JSON - #429

Open
RishavTiwari25 wants to merge 1 commit into
getlago:mainfrom
RishavTiwari25:fix/non-json-error-body
Open

RishavTiwari25 wants to merge 1 commit into
getlago:mainfrom
RishavTiwari25:fix/non-json-error-body

Conversation

@RishavTiwari25

Copy link
Copy Markdown

Problem

When the API (or anything in front of it) answers an error with a body that is not JSON, the client reports the wrong error:

r = Response(502, content=b"<html>...502 Bad Gateway...</html>",
             request=Request("GET", "https://api.getlago.com/api/v1/customers/123"))
verify_response(r)
# LagoApiError: status_code=500, url=None, detail='unexpected character, expected a JSON value'

An empty error body gives the same thing (detail='Input is a zero-length, empty document'). This happens for HTML 502/503/504 pages from load balancers and reverse proxies (self-hosted Lago behind nginx, or a gateway timeout), so code that branches on exc.status_code (retry on 502/503, treat 404 as missing) sees a 500, and the error does not say which request failed.

Root cause

verify_response calls from_json(response) on every non-2xx response before raising. from_json raises its own LagoApiError(status_code=500, url=None, ...) when decoding fails, and that error escapes instead of the one built from the response.

Fix

Decode the error body inside a try; if it is not JSON, keep the raw text (or None for an empty body) as the error's response. The raised LagoApiError then always carries the real status code, URL and headers. JSON error bodies are handled as before.

Tests

  • test_verify_response_with_non_json_error_body (HTML 502, plain-text 503, empty 404): asserts status code, url and raw body. All three fail on main.
  • test_verify_response_with_json_error_body: JSON error bodies are still decoded.

python -m pytest tests: 468 passed. ruff check . and ruff format --check . clean.

verify_response decoded every error body as JSON. When it was not JSON (an
HTML 502/503 page from a load balancer or proxy, or an empty body), the
decoding error escaped instead: a LagoApiError with status_code 500, url
None and the orjson message as detail, so callers could not tell a 502 from
a 404 or see which request failed. The raw body is now kept as the error's
response and the real status code and url are reported.
@cla-check-bot

cla-check-bot Bot commented Oct 26, 2026

Copy link
Copy Markdown

Welcome, @RishavTiwari25!

Thanks for your first contribution!

Before we proceed with the review, please sign the Fiduciary License Agreement:

Sign the FLA

Once signed, this PR will be automatically updated.

@cla-check-bot

cla-check-bot Bot commented Oct 26, 2026

Copy link
Copy Markdown

Thanks, @RishavTiwari25! 🎉

Your CLA has been signed and is now on file. We'll proceed with the review shortly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant