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.renderpdf.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.renderpdf.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.renderpdf.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.renderpdf.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

Coming Soon - This endpoint is not yet available.

import requests
import os
from datetime import datetime

response = requests.post(
    'https://api.renderpdf.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.renderpdf.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.renderpdf.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.renderpdf.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.renderpdf.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")

Merge PDFs

import requests
import base64
import os

def merge_pdfs(pdf_sources, storage_filename=None):
    """Merge multiple PDFs into one."""
    pdfs = []

    for source in pdf_sources:
        if source.startswith('http'):
            pdfs.append({'url': source})
        else:
            # Local file path
            with open(source, 'rb') as f:
                pdfs.append({'data': base64.b64encode(f.read()).decode('utf-8')})

    payload = {'pdfs': pdfs}

    if storage_filename:
        payload['storage'] = {
            'enabled': True,
            'filename': storage_filename
        }

    response = requests.post(
        'https://api.pdfapi.dev/v1/pdf/merge',
        headers={
            'Authorization': f'Bearer {os.environ["PDFAPI_KEY"]}',
            'Content-Type': 'application/json',
        },
        json=payload
    )

    if response.status_code != 200:
        raise Exception(response.json()['error']['message'])

    file_id = response.headers.get('X-File-ID')
    return response.content, file_id


# Usage
pdf_content, file_id = merge_pdfs([
    'cover.pdf',
    'https://example.com/content.pdf',
    'appendix.pdf'
], storage_filename='complete-report.pdf')

with open('merged.pdf', 'wb') as f:
    f.write(pdf_content)

print(f'Merged PDF saved. File ID: {file_id}')

PDF with Compression

import requests
import os

def generate_compressed_pdf(markdown, compression_level='medium'):
    """Generate a PDF with compression."""
    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,
            'options': {
                'format': 'A4',
                'compression': {
                    'enabled': True,
                    'level': compression_level  # 'none', 'low', 'medium', 'high'
                }
            }
        }
    )

    return response.content


# Usage
pdf = generate_compressed_pdf('# Large Report\n\nContent here...', 'high')
with open('compressed.pdf', 'wb') as f:
    f.write(pdf)

PDF Metadata Operations

import requests
import base64
import os

def set_pdf_metadata(pdf_path, metadata):
    """Set metadata on a PDF."""
    with open(pdf_path, 'rb') as f:
        pdf_base64 = base64.b64encode(f.read()).decode('utf-8')

    response = requests.post(
        'https://api.pdfapi.dev/v1/pdf/metadata',
        headers={
            'Authorization': f'Bearer {os.environ["PDFAPI_KEY"]}',
            'Content-Type': 'application/json',
        },
        json={
            'pdf': pdf_base64,
            'metadata': metadata
        }
    )

    return response.content


def get_pdf_metadata(pdf_source):
    """Get metadata from a PDF (path or URL)."""
    payload = {}

    if pdf_source.startswith('http'):
        payload['url'] = pdf_source
    else:
        with open(pdf_source, 'rb') as f:
            payload['pdf'] = base64.b64encode(f.read()).decode('utf-8')

    response = requests.post(
        'https://api.pdfapi.dev/v1/pdf/metadata/get',
        headers={
            'Authorization': f'Bearer {os.environ["PDFAPI_KEY"]}',
            'Content-Type': 'application/json',
        },
        json=payload
    )

    return response.json()['data']


# Usage - Set metadata
pdf_with_meta = set_pdf_metadata('document.pdf', {
    'title': 'Q4 2025 Report',
    'author': 'Finance Team',
    'subject': 'Quarterly Financial Summary',
    'keywords': 'finance, quarterly, 2025'
})
with open('document-with-metadata.pdf', 'wb') as f:
    f.write(pdf_with_meta)

# Usage - Get metadata
metadata = get_pdf_metadata('document.pdf')
print(f"Title: {metadata.get('title')}")
print(f"Author: {metadata.get('author')}")

File Storage Operations

import requests
import os

class FileStorage:
    """Manage stored PDF files."""

    def __init__(self):
        self.base_url = 'https://api.pdfapi.dev/v1'
        self.headers = {
            'Authorization': f'Bearer {os.environ["PDFAPI_KEY"]}'
        }

    def list_files(self, page=1, limit=20):
        """List all stored files."""
        response = requests.get(
            f'{self.base_url}/files',
            headers=self.headers,
            params={'page': page, 'limit': limit}
        )
        return response.json()

    def get_file(self, file_id):
        """Get details of a specific file."""
        response = requests.get(
            f'{self.base_url}/files/{file_id}',
            headers=self.headers
        )
        return response.json()['data']

    def download_file(self, file_id):
        """Download a stored file."""
        response = requests.get(
            f'{self.base_url}/files/{file_id}/download',
            headers=self.headers
        )
        return response.content

    def delete_file(self, file_id):
        """Delete a stored file."""
        response = requests.delete(
            f'{self.base_url}/files/{file_id}',
            headers=self.headers
        )
        return response.status_code == 204


# Usage
storage = FileStorage()

# List files
files = storage.list_files()
for f in files['data']:
    print(f"{f['filename']} - {f['size']} bytes")

# Download a file
file_content = storage.download_file('f47ac10b-58cc-4372-a567-0e02b2c3d479')
with open('downloaded.pdf', 'wb') as f:
    f.write(file_content)

Webhook Configuration

import requests
import os

class WebhookManager:
    """Manage webhook configuration."""

    def __init__(self):
        self.base_url = 'https://api.pdfapi.dev/v1'
        self.headers = {
            'Authorization': f'Bearer {os.environ["PDFAPI_KEY"]}',
            'Content-Type': 'application/json'
        }

    def configure(self, url, events=None, secret=None):
        """Configure webhook endpoint."""
        payload = {'url': url}
        if events:
            payload['events'] = events
        if secret:
            payload['secret'] = secret

        response = requests.post(
            f'{self.base_url}/webhooks/config',
            headers=self.headers,
            json=payload
        )
        return response.json()

    def get_config(self):
        """Get current webhook configuration."""
        response = requests.get(
            f'{self.base_url}/webhooks/config',
            headers=self.headers
        )
        return response.json().get('data')

    def delete_config(self):
        """Delete webhook configuration."""
        response = requests.delete(
            f'{self.base_url}/webhooks/config',
            headers=self.headers
        )
        return response.status_code == 204


# Usage
webhooks = WebhookManager()

# Configure webhook
config = webhooks.configure(
    url='https://your-app.com/webhooks/pdfapi',
    events=['pdf.generated', 'pdf.failed'],
    secret='your-webhook-secret'
)
print(f"Webhook ID: {config['data']['id']}")

# Get configuration
current_config = webhooks.get_config()
print(f"Webhook URL: {current_config['url']}")

Flask Webhook Handler

from flask import Flask, request, abort
import hmac
import hashlib
import json
import os

app = Flask(__name__)

def verify_webhook_signature(payload, signature, timestamp, secret):
    """Verify webhook signature."""
    signed_payload = f"{timestamp}.{json.dumps(payload, separators=(',', ':'))}"
    expected_signature = hmac.new(
        secret.encode('utf-8'),
        signed_payload.encode('utf-8'),
        hashlib.sha256
    ).hexdigest()

    return hmac.compare_digest(signature, expected_signature)


@app.route('/webhooks/pdfapi', methods=['POST'])
def webhook_handler():
    signature = request.headers.get('X-Webhook-Signature')
    timestamp = request.headers.get('X-Webhook-Timestamp')

    if not verify_webhook_signature(
        request.json,
        signature,
        timestamp,
        os.environ['WEBHOOK_SECRET']
    ):
        abort(401)

    event_type = request.json['type']
    event_data = request.json['data']

    if event_type == 'pdf.generated':
        print(f"PDF generated: {event_data['file_id']}")
        # Process successful generation
    elif event_type == 'pdf.failed':
        print(f"PDF failed: {event_data['error']}")
        # Handle failure

    return 'OK', 200


if __name__ == '__main__':
    app.run(debug=True)