Skip to content
TryDocify

Guide

Python PDF generation with a REST API

Generate PDFs in Python with requests: a small helper with error handling, and a Flask route that returns a PDF download. No system packages to install.

Updated

The approach

Python PDF libraries either draw pages by coordinates, which makes tables and page breaks your problem, or convert HTML, which needs system packages or a headless browser on every server. With a JSON to PDF API, Python builds a dictionary and sends it. Pagination, repeated table headers and page numbers are handled by the layout engine.

The examples use requests (pip install requests) and run on Python 3.9 or later.

Generate a PDF with requests

generate_pdf(document) returns the PDF as bytes and raises with the problem code when the API rejects the document. Set TRYDOCIFY_API_KEY first.

Python · generate_pdf.py
import os

import requests

API_URL = "https://api.trydocify.com/api/v1/generate"


def generate_pdf(document: dict) -> bytes:
    response = requests.post(
        API_URL,
        json=document,
        headers={"X-API-Key": os.environ["TRYDOCIFY_API_KEY"]},
        timeout=30,
    )
    if not response.ok:
        # RFC 9457 problem document with a stable "code"

        problem = response.json()
        raise RuntimeError(f"{response.status_code} {problem['code']}: {problem.get('detail')}")
    return response.content


pdf = generate_pdf({
    "test": True,  # free, watermarked render while you build; remove for production

    "title": "Monthly usage report",
    "blocks": [
        {"type": "paragraph", "text": "Usage for October 2026."},
        {
            "type": "table",
            "columns": [{"header": "Service"}, {"header": "Requests", "align": "right"}],
            "rows": [["Search", 18240], ["Exports", 3120]],
            "footer": [["Total", 21360]],
        },
    ],
})

with open("report.pdf", "wb") as f:
    f.write(pdf)
print(f"Saved report.pdf ({len(pdf)} bytes)")

"test": True makes the render free and watermarked while you work on the layout (200 test renders a day per key). Remove it in production. Validation errors are never charged, and their errors field names the exact JSON path to fix, such as blocks[1].rows[2].

Return a PDF from Flask

To serve a receipt or report on demand, build the document from your data, call the API and stream the bytes back with send_file:

Python · app.py
import io
import os

import requests
from flask import Flask, abort, send_file

app = Flask(__name__)
API_URL = "https://api.trydocify.com/api/v1/generate"


@app.get("/orders/<order_id>/receipt.pdf")
def receipt(order_id: str):
    document = {
        "test": app.debug,  # watermarked and free while developing

        "title": f"Receipt for order {order_id}",
        "blocks": [
            {"type": "keyValue", "items": [{"key": "Order", "value": order_id}]},
            {
                "type": "table",
                "columns": [{"header": "Item"}, {"header": "Amount", "align": "right"}],
                "rows": [["Annual license", "$490.00"]],
                "footer": [["Total", "$490.00"]],
            },
        ],
    }

    response = requests.post(
        API_URL,
        json=document,
        headers={"X-API-Key": os.environ["TRYDOCIFY_API_KEY"]},
        timeout=30,
    )
    if not response.ok:
        app.logger.error("PDF generation failed: %s", response.text)
        abort(502)

    return send_file(
        io.BytesIO(response.content),
        mimetype="application/pdf",
        download_name=f"receipt-{order_id}.pdf",
    )
  • app.debug switches test mode on while you develop, so local renders are free.
  • download_name sets the file name. Add as_attachment=True to force a download instead of opening the PDF in the browser.
  • In Django, return HttpResponse(response.content, content_type="application/pdf"). In FastAPI, use Response(content=..., media_type="application/pdf").

Tips for production

  • Always pass a timeout to requests.post; typical documents render in milliseconds.
  • Retry 429 and 503 after the number of seconds in Retry-After. Other errors won't succeed on retry.
  • Format numbers and dates in Python before sending them. Values are printed exactly as written, so f"${amount:,.2f}" gives you full control.
  • For images, base64-encode the file: "data:image/png;base64," + base64.b64encode(data).decode().

The document format is the same in every language. The JSON to PDF tutorial walks through it block by block.

Try it with your own data

Every plan includes 200 free test renders a day. Add "test": true while you build, and remove it when you go live.