The request or the response
413 Payload Too Large when sending HTML to render
Small documents render and large ones are rejected before anything happens. The threshold is consistent, which distinguishes this from a timeout.
What is actually happening
The request body exceeded a limit. Almost always this is base64 images inside the HTML: base64 adds about a third to the size of every embedded asset, so a handful of photos turns a 40KB document into a request of several megabytes.
Confirming it is this and not something that looks like it
Log the byte length of the request body. If it is far larger than the visible HTML, embedded assets are the cause.
The fix
Keep large images as URLs and reserve embedding for small fixed assets like logos. Resize before embedding if you must embed. The rule that works is a size threshold rather than a judgement per image.
EMBED_LIMIT = 100 * 1024 # bytes, before base64 expands it by a third
def asset_src(path: Path, public_url: str) -> str:
"""Small and fixed: embed it, and remove a network dependency.
Large or user-supplied: link it, and keep the request small."""
if path.stat().st_size <= EMBED_LIMIT:
return data_uri(path)
return public_urlIf that was not it
These produce the same symptom often enough to be worth ruling out before assuming the fix above did not work.
- An embedded font, which is often larger than any single image
- A proxy with a lower body limit than the endpoint itself
- Repeating the same base64 image on every row of a table
- Sending a whole CSS framework inline when the document uses a dozen rules
Frequently asked
Does this happen with every rendering engine?
The behaviour behind it is not specific to one tool. The request body exceeded a limit. Anything rendering HTML to a paged medium has to make the same decision, so the fix travels with you if you change how the render happens.
Will this show up as an error in my logs?
Yes, this one surfaces as a thrown error or a failed status, which is why it is at least findable. The harder half is that the message often names the call that was in flight rather than the thing that actually failed.
Related failures
Problems people arrive at from the same starting point, or mistake for this one.
401 Unauthorized from the render API
The key that arrived is not the key you think you sent.
The downloaded PDF is corrupted and will not open
The bytes were treated as text somewhere.
The PDF opens in the browser instead of downloading
Content-Disposition is absent or set to inline.
429 Too Many Requests when generating PDFs in bulk
Unbounded concurrency.
The PDF is truncated or ends mid-document
The response was cut off.
CSS for paged documents, and what actually works
Which parts of the paged media specification a browser-based render implements.
Everything that goes wrong, by category
The full list, grouped by where in the pipeline it breaks.
Paste your markup and see the rendered document, without signing up.