Python integration
Generate PDFs from HTML in Python
Call the CheapPDF API with the popular requests library. Send HTML or a URL, decode the response and save the PDF, with timeouts and retries built in.
# pip install requests
import base64, os, requests
resp = requests.post(
"https://www.cheap-pdf.com/generate-pdf-public",
data={
"apikey": os.environ["CHEAPPDF_API_KEY"],
"filename": "hello.pdf",
"fileorhtmlorurl": "<h1>Hello from Python</h1>",
},
timeout=60,
)
result = resp.json()
with open("hello.pdf", "wb") as f:
f.write(base64.b64decode(result["data"]["base64_pdf"]))Prerequisites
- Python 3.8 or later and
pip install requests. - Your API key in the
CHEAPPDF_API_KEYenvironment variable. Never hard-code it or commit it.
A reusable client
This class authenticates every call, sets separate connect and read timeouts, retries only transient failures, and raises a CheapPdfError that carries the API’s error code and HTTP status.
# cheappdf.py
import base64
import os
import requests
from requests.adapters import HTTPAdapter
from urllib3.util.retry import Retry
ENDPOINT = "https://www.cheap-pdf.com/generate-pdf-public"
class CheapPdfError(Exception):
def __init__(self, message, code=None, status=0):
super().__init__(message)
self.code = code
self.status = status
class CheapPdf:
def __init__(self, api_key=None, timeout=60):
self.api_key = api_key or os.environ.get("CHEAPPDF_API_KEY", "")
if not self.api_key:
raise ValueError("CHEAPPDF_API_KEY is not set")
self.timeout = timeout
self.session = requests.Session()
# Retry connection errors and 5xx only. POST must be allowed explicitly.
retry = Retry(
total=2,
backoff_factor=1,
status_forcelist=[500, 502, 503, 504],
allowed_methods=["POST"],
raise_on_status=False,
)
self.session.mount("https://", HTTPAdapter(max_retries=retry))
def from_html(self, html, filename="document.pdf", **options):
return self._send(html, filename, options)
def from_url(self, url, filename="page.pdf", **options):
return self._send(url, filename, options)
def _send(self, source, filename, options):
data = {"apikey": self.api_key, "filename": filename, "fileorhtmlorurl": source, **options}
try:
resp = self.session.post(ENDPOINT, data=data, timeout=(10, self.timeout))
except requests.RequestException as exc:
raise CheapPdfError(f"Network error: {exc}") from exc
try:
result = resp.json()
except ValueError:
result = {}
if resp.status_code != 200 or not result.get("success"):
raise CheapPdfError(
result.get("message", f"Conversion failed (HTTP {resp.status_code})"),
code=result.get("error"),
status=resp.status_code,
)
pdf = base64.b64decode(result["data"]["base64_pdf"])
if not pdf.startswith(b"%PDF"):
raise CheapPdfError("Response was not a valid PDF")
return pdfUsing it
from cheappdf import CheapPdf, CheapPdfError
client = CheapPdf()
try:
pdf = client.from_html(
"<h1>Invoice #1042</h1><p>Total: $9.00</p>",
filename="invoice-1042.pdf",
width=800,
height=1100,
)
with open("invoice-1042.pdf", "wb") as f:
f.write(pdf)
except CheapPdfError as err:
if err.code == "free_plan_limit_reached":
print("Monthly free limit reached. Upgrade to continue.")
elif err.status == 400:
print(f"Request rejected [{err.code}]: {err}") # fix the input, do not retry
else:
print(f"Temporary failure, try again later: {err}")Request parameters and response handling
Required fields: apikey, fileorhtmlorurl and filename. Optional: width and height in pixels, landscape and printBackground (pass "true" or "false"). To upload an .html file (up to 5 MB), use files={"fileorhtmlorurl": open("page.html", "rb")} instead of putting it in data.
On success the JSON has success: true and the PDF in data.base64_pdf. On failure, message explains why and error holds a code such as invalid_api_key.
Batch conversion with limited concurrency
Use a small thread pool and collect failures per document. Request rate limits are not published, so keep the worker count low.
from concurrent.futures import ThreadPoolExecutor, as_completed
from cheappdf import CheapPdf, CheapPdfError
client = CheapPdf()
reports = {"q1": "<h1>Q1 report</h1>", "q2": "<h1>Q2 report</h1>", "q3": "<h1>Q3 report</h1>"}
def build(name, html):
pdf = client.from_html(html, filename=f"{name}.pdf")
with open(f"{name}.pdf", "wb") as f:
f.write(pdf)
return name
failed = {}
with ThreadPoolExecutor(max_workers=3) as pool: # keep concurrency low
futures = {pool.submit(build, n, h): n for n, h in reports.items()}
for future in as_completed(futures):
name = futures[future]
try:
future.result()
except CheapPdfError as err:
failed[name] = str(err)
print("Failed:", failed or "none")Run large batches from a worker or scheduled job (for example Celery or cron), not inside a web request. The free plan allows 10 conversions per day, so batches need the Premium plan.
Python FAQ
Which Python library should I use?
requests is a good default. Any HTTP client works because the API is a single form POST. For async code, httpx or aiohttp can send the same fields.
How do I save the PDF in Python?
Decode data.base64_pdf with base64.b64decode() and write the bytes to a file opened in 'wb' mode.
Should I retry failed requests?
Retry network errors, timeouts and 5xx responses a couple of times with backoff. Do not retry 400 responses, because the same request will fail again.
Can I use it from Django or Flask?
Yes. Render a template to an HTML string, call from_html(), and return the bytes in an HttpResponse or Response with the application/pdf content type.
Add PDF generation to your Python app
Create an API key and run the quick start in minutes.