Python Examples
Example code for using PDF API with Python.
Python Examples
Use PDF API with Python using the requests library.
Installation
pip install requests
Basic Example
import requests
import os
def generate_pdf(markdown, theme='default', options=None):
"""Generate a PDF from markdown content."""
response = requests.post(
'https://api.pdfapi.dev/v1/pdf/markdown',
headers={
'Authorization': f'Bearer {os.environ["PDFAPI_KEY"]}',
'Content-Type': 'application/json',
},
json={
'markdown': markdown,
'theme': theme,
'options': options or {}
}
)
if response.status_code != 200:
error = response.json()
raise Exception(error['error']['message'])
return response.content
# Usage
pdf = generate_pdf('# Hello World\n\nThis is my first PDF!')
with open('hello.pdf', 'wb') as f:
f.write(pdf)
print('PDF saved to hello.pdf')
With Options
import requests
import os
markdown = """# Monthly Report
## Executive Summary
- Revenue: $45,678
- Users: 1,234
- Growth: 15%
## Details
| Metric | Q1 | Q2 | Q3 |
|--------|----|----|----|
| Users | 1000 | 1100 | 1234 |
| Revenue | $30K | $38K | $46K |
## Conclusion
Strong growth trajectory continues.
"""
response = requests.post(
'https://api.pdfapi.dev/v1/pdf/markdown',
headers={
'Authorization': f'Bearer {os.environ["PDFAPI_KEY"]}',
'Content-Type': 'application/json',
},
json={
'markdown': markdown,
'theme': 'corporate',
'options': {
'format': 'A4',
'margin': {
'top': '25mm',
'bottom': '25mm',
'left': '20mm',
'right': '20mm'
},
'header': '<div style="text-align: center; font-size: 10px;">Monthly Report - Q3 2025</div>',
'footer': '<div style="text-align: center; font-size: 10px;">Page <span class="pageNumber"></span></div>'
}
}
)
with open('report.pdf', 'wb') as f:
f.write(response.content)
HTML to PDF
import requests
import os
html = """
<!DOCTYPE html>
<html>
<head>
<style>
body { font-family: Arial, sans-serif; padding: 40px; }
h1 { color: #2563eb; }
table { width: 100%; border-collapse: collapse; margin: 20px 0; }
th, td { border: 1px solid #e5e7eb; padding: 12px; text-align: left; }
th { background: #f9fafb; }
</style>
</head>
<body>
<h1>Invoice #1234</h1>
<table>
<tr><th>Item</th><th>Qty</th><th>Price</th></tr>
<tr><td>Widget Pro</td><td>2</td><td>$59.98</td></tr>
<tr><td>Gadget Plus</td><td>1</td><td>$49.99</td></tr>
</table>
<p><strong>Total: $109.97</strong></p>
</body>
</html>
"""
response = requests.post(
'https://api.pdfapi.dev/v1/pdf/html',
headers={
'Authorization': f'Bearer {os.environ["PDFAPI_KEY"]}',
'Content-Type': 'application/json',
},
json={
'html': html,
'options': {
'format': 'A4',
'print_background': True
}
}
)
with open('invoice.pdf', 'wb') as f:
f.write(response.content)
URL to PDF
import requests
import os
response = requests.post(
'https://api.pdfapi.dev/v1/pdf/url',
headers={
'Authorization': f'Bearer {os.environ["PDFAPI_KEY"]}',
'Content-Type': 'application/json',
},
json={
'url': 'https://example.com',
'options': {
'format': 'A4',
'wait_for': 2000,
'full_page': True,
'viewport': {
'width': 1920,
'height': 1080
}
}
}
)
with open('webpage.pdf', 'wb') as f:
f.write(response.content)
Template to PDF
import requests
import os
from datetime import datetime
response = requests.post(
'https://api.pdfapi.dev/v1/pdf/template',
headers={
'Authorization': f'Bearer {os.environ["PDFAPI_KEY"]}',
'Content-Type': 'application/json',
},
json={
'template': 'invoice',
'data': {
'invoice_number': f'INV-{datetime.now().strftime("%Y%m%d")}-001',
'date': datetime.now().strftime('%Y-%m-%d'),
'due_date': '2025-02-18',
'customer': {
'name': 'Acme Corporation',
'email': 'billing@acme.com',
'address': '123 Business St, Suite 100\nNew York, NY 10001'
},
'items': [
{'description': 'Widget Pro', 'quantity': 2, 'unit_price': 29.99},
{'description': 'Gadget Plus', 'quantity': 1, 'unit_price': 49.99},
{'description': 'Service Fee', 'quantity': 1, 'unit_price': 9.99}
],
'subtotal': 119.96,
'tax': 10.80,
'total': 130.76
}
}
)
with open('invoice.pdf', 'wb') as f:
f.write(response.content)
PDFClient Class
import requests
import os
from typing import Optional, Dict, Any, List
class PDFClient:
"""Python client for PDF API."""
def __init__(self, api_key: Optional[str] = None):
self.api_key = api_key or os.environ.get('PDFAPI_KEY')
self.base_url = 'https://api.pdfapi.dev/v1'
if not self.api_key:
raise ValueError('API key is required')
def _request(self, method: str, endpoint: str, **kwargs) -> requests.Response:
"""Make an API request."""
headers = {
'Authorization': f'Bearer {self.api_key}',
'Content-Type': 'application/json',
}
response = requests.request(
method,
f'{self.base_url}{endpoint}',
headers=headers,
**kwargs
)
if response.status_code == 429:
raise RateLimitError(response.json()['error']['message'])
elif response.status_code >= 400:
raise APIError(response.json()['error']['message'])
return response
def markdown_to_pdf(
self,
markdown: str,
theme: str = 'default',
options: Optional[Dict[str, Any]] = None
) -> bytes:
"""Convert markdown to PDF."""
response = self._request(
'POST',
'/pdf/markdown',
json={'markdown': markdown, 'theme': theme, 'options': options or {}}
)
return response.content
def html_to_pdf(
self,
html: str,
options: Optional[Dict[str, Any]] = None
) -> bytes:
"""Convert HTML to PDF."""
response = self._request(
'POST',
'/pdf/html',
json={'html': html, 'options': options or {}}
)
return response.content
def url_to_pdf(
self,
url: str,
options: Optional[Dict[str, Any]] = None
) -> bytes:
"""Convert URL to PDF."""
response = self._request(
'POST',
'/pdf/url',
json={'url': url, 'options': options or {}}
)
return response.content
def get_usage(self) -> Dict[str, Any]:
"""Get current usage statistics."""
response = self._request('GET', '/usage')
return response.json()['data']
def list_themes(self) -> List[Dict[str, Any]]:
"""List available themes."""
response = self._request('GET', '/themes')
return response.json()['data']
class APIError(Exception):
"""API error exception."""
pass
class RateLimitError(APIError):
"""Rate limit exceeded exception."""
pass
# Usage
client = PDFClient()
# Generate PDF
pdf = client.markdown_to_pdf('# Hello World', theme='corporate')
with open('hello.pdf', 'wb') as f:
f.write(pdf)
# Check usage
usage = client.get_usage()
print(f"Used {usage['pdfs_generated']} of {usage['pdfs_limit']} PDFs")
Django Integration
# views.py
from django.http import HttpResponse
from django.views import View
import requests
import os
class GeneratePDFView(View):
def post(self, request):
markdown = request.POST.get('markdown', '')
theme = request.POST.get('theme', 'default')
response = requests.post(
'https://api.pdfapi.dev/v1/pdf/markdown',
headers={
'Authorization': f'Bearer {os.environ["PDFAPI_KEY"]}',
'Content-Type': 'application/json',
},
json={'markdown': markdown, 'theme': theme}
)
if response.status_code != 200:
return HttpResponse(status=response.status_code)
http_response = HttpResponse(
response.content,
content_type='application/pdf'
)
http_response['Content-Disposition'] = 'attachment; filename="document.pdf"'
return http_response
Flask Integration
from flask import Flask, request, send_file
import requests
import os
from io import BytesIO
app = Flask(__name__)
@app.route('/generate-pdf', methods=['POST'])
def generate_pdf():
data = request.json
response = requests.post(
'https://api.pdfapi.dev/v1/pdf/markdown',
headers={
'Authorization': f'Bearer {os.environ["PDFAPI_KEY"]}',
'Content-Type': 'application/json',
},
json={
'markdown': data.get('markdown', ''),
'theme': data.get('theme', 'default')
}
)
if response.status_code != 200:
return response.json(), response.status_code
return send_file(
BytesIO(response.content),
mimetype='application/pdf',
as_attachment=True,
download_name='document.pdf'
)
if __name__ == '__main__':
app.run(debug=True)
Batch Processing
import requests
import os
import time
from pathlib import Path
from concurrent.futures import ThreadPoolExecutor, as_completed
def generate_single_pdf(markdown_file: Path, output_dir: Path) -> dict:
"""Generate a PDF from a single markdown file."""
try:
markdown = markdown_file.read_text()
response = requests.post(
'https://api.pdfapi.dev/v1/pdf/markdown',
headers={
'Authorization': f'Bearer {os.environ["PDFAPI_KEY"]}',
'Content-Type': 'application/json',
},
json={'markdown': markdown, 'theme': 'default'}
)
if response.status_code == 429:
# Rate limited, wait and retry
time.sleep(60)
return generate_single_pdf(markdown_file, output_dir)
if response.status_code != 200:
return {'file': str(markdown_file), 'success': False, 'error': response.json()['error']['message']}
output_file = output_dir / f'{markdown_file.stem}.pdf'
output_file.write_bytes(response.content)
return {'file': str(markdown_file), 'success': True, 'output': str(output_file)}
except Exception as e:
return {'file': str(markdown_file), 'success': False, 'error': str(e)}
def batch_convert(input_dir: str, output_dir: str, max_workers: int = 3):
"""Convert all markdown files in a directory to PDFs."""
input_path = Path(input_dir)
output_path = Path(output_dir)
output_path.mkdir(exist_ok=True)
markdown_files = list(input_path.glob('*.md'))
results = []
# Use thread pool with limited workers to respect rate limits
with ThreadPoolExecutor(max_workers=max_workers) as executor:
futures = {
executor.submit(generate_single_pdf, f, output_path): f
for f in markdown_files
}
for future in as_completed(futures):
result = future.result()
results.append(result)
if result['success']:
print(f"✓ {result['file']} -> {result['output']}")
else:
print(f"✗ {result['file']}: {result['error']}")
# Small delay between completions
time.sleep(0.5)
return results
# Usage
results = batch_convert('documents/', 'output/')
successful = sum(1 for r in results if r['success'])
print(f"\nCompleted: {successful}/{len(results)} files converted")