Generate Branded PDF Reports from JSON Data Using an API (2026 Guide)
Learn how to automate branded PDF report generation from structured JSON data using the DocForge API. No design tools required — just code and templates.

Every business generates reports — monthly analytics, client-facing summaries, compliance certificates, financial statements. Most teams still produce these manually: copy data into a spreadsheet, paste it into a Word doc, export to PDF, email it out. It's tedious, error-prone, and doesn't scale.
The document automation market tells the story: the global Document Generation Software Market was valued at $4.05 billion in 2025 and is projected to reach $9.77 billion by 2035, growing at 9.2% CAGR. Teams that automate this process today gain a compounding operational advantage.
This guide shows you how to generate fully branded, professional PDF reports directly from JSON data using the DocForge API. No InDesign, no Word, no manual formatting — just structured data in, polished PDF out.
What We're Building
A Python service that:
- Accepts a JSON payload containing report data
- Selects a template (executive summary, analytics report, compliance certificate, etc.)
- Calls the DocForge API to render the PDF with your branding
- Returns a downloadable PDF or uploads it to cloud storage
By the end, you'll be able to generate a branded 10-page report from a JSON object in under 3 seconds.
Why Template-Based PDF Generation Beats Manual Approaches
The traditional approach to report generation has real costs:
- Time: Even a well-practiced analyst takes 45-90 minutes to produce a monthly report manually
- Inconsistency: Different team members produce reports with different formatting, making cross-period comparisons harder
- Error risk: Manual copy-paste introduces data errors that can reach clients or regulators
- Scalability ceiling: A team that manually produces 20 reports per month can't suddenly handle 200
API-based document generation eliminates all four problems. Once your templates are defined, reports are generated consistently in seconds at any volume.
Step 1: Set Up the DocForge Client
import httpx
import base64
from pathlib import Path
from typing import Union
from dataclasses import dataclass
DOCFORGE_API_KEY = "YOUR_API_KEY"
DOCFORGE_BASE_URL = "https://apivult.com/docforge/v1"
@dataclass
class GeneratedDocument:
pdf_bytes: bytes
page_count: int
file_size_kb: float
generation_time_ms: int
document_id: str
def generate_pdf_from_template(
template_id: str,
data: dict,
options: dict = None
) -> GeneratedDocument:
"""
Generate a PDF from a template and data payload.
Args:
template_id: The DocForge template identifier
data: The data to inject into the template
options: Optional rendering options (orientation, paper size, etc.)
Returns:
GeneratedDocument with PDF bytes and metadata
"""
payload = {
"template_id": template_id,
"data": data,
"output_format": "pdf",
"options": options or {
"paper_size": "A4",
"orientation": "portrait",
"compress": True
}
}
response = httpx.post(
f"{DOCFORGE_BASE_URL}/generate",
headers={
"X-RapidAPI-Key": DOCFORGE_API_KEY,
"Content-Type": "application/json"
},
json=payload,
timeout=30
)
response.raise_for_status()
data_response = response.json()
pdf_b64 = data_response.get("document_base64", "")
pdf_bytes = base64.b64decode(pdf_b64)
return GeneratedDocument(
pdf_bytes=pdf_bytes,
page_count=data_response.get("page_count", 0),
file_size_kb=len(pdf_bytes) / 1024,
generation_time_ms=data_response.get("generation_time_ms", 0),
document_id=data_response.get("document_id", "")
)
def save_pdf(document: GeneratedDocument, output_path: str) -> str:
"""Save generated PDF to disk."""
path = Path(output_path)
path.write_bytes(document.pdf_bytes)
print(f"PDF saved: {path} ({document.file_size_kb:.1f} KB, {document.page_count} pages, "
f"{document.generation_time_ms}ms)")
return str(path)Step 2: Define Your Report Data Models
Structure your report data with Pydantic for validation before sending to the API:
from pydantic import BaseModel, Field
from typing import Optional
from datetime import date
class MetricItem(BaseModel):
label: str
value: Union[float, int, str]
unit: str = ""
change_pct: Optional[float] = None # e.g., +12.5 means +12.5% vs prior period
trend: Optional[str] = None # "up", "down", "flat"
class ChartData(BaseModel):
title: str
chart_type: str # "bar", "line", "pie", "area"
labels: list[str]
datasets: list[dict]
class ReportSection(BaseModel):
heading: str
body: str
metrics: list[MetricItem] = []
chart: Optional[ChartData] = None
table: Optional[dict] = None # {"headers": [...], "rows": [[...]]}
class BrandingConfig(BaseModel):
company_name: str
logo_url: Optional[str] = None
primary_color: str = "#1a73e8"
secondary_color: str = "#f8f9fa"
footer_text: Optional[str] = None
class MonthlyAnalyticsReport(BaseModel):
report_title: str
report_subtitle: str = ""
report_date: date
period_label: str # e.g., "March 2026"
prepared_for: str # Client name or "Internal"
prepared_by: str
branding: BrandingConfig
executive_summary: str
key_metrics: list[MetricItem]
sections: list[ReportSection]
appendix: Optional[str] = NoneStep 3: Build Report Generators for Common Report Types
Monthly Analytics Report
from datetime import date
def generate_analytics_report(
report_data: MonthlyAnalyticsReport,
output_path: str = None
) -> GeneratedDocument:
"""Generate a branded monthly analytics report PDF."""
template_data = {
"title": report_data.report_title,
"subtitle": report_data.report_subtitle,
"date": report_data.report_date.strftime("%B %d, %Y"),
"period": report_data.period_label,
"prepared_for": report_data.prepared_for,
"prepared_by": report_data.prepared_by,
"branding": {
"company_name": report_data.branding.company_name,
"logo_url": report_data.branding.logo_url,
"primary_color": report_data.branding.primary_color,
"secondary_color": report_data.branding.secondary_color,
"footer_text": report_data.branding.footer_text or f"© {date.today().year} {report_data.branding.company_name}"
},
"executive_summary": report_data.executive_summary,
"key_metrics": [m.model_dump() for m in report_data.key_metrics],
"sections": [s.model_dump() for s in report_data.sections],
"appendix": report_data.appendix
}
document = generate_pdf_from_template(
template_id="monthly_analytics_report_v2",
data=template_data,
options={
"paper_size": "A4",
"orientation": "portrait",
"include_table_of_contents": len(report_data.sections) > 3,
"watermark": None
}
)
if output_path:
save_pdf(document, output_path)
return documentClient Compliance Certificate
def generate_compliance_certificate(
client_name: str,
compliance_standard: str, # e.g., "SOC 2 Type II", "ISO 27001"
assessment_date: date,
valid_until: date,
issuer_name: str,
issuer_title: str,
scope: str,
findings_summary: str,
branding: BrandingConfig,
output_path: str = None
) -> GeneratedDocument:
"""Generate a compliance certificate PDF."""
template_data = {
"certificate_type": "compliance",
"client_name": client_name,
"compliance_standard": compliance_standard,
"assessment_date": assessment_date.strftime("%B %d, %Y"),
"valid_until": valid_until.strftime("%B %d, %Y"),
"issuer_name": issuer_name,
"issuer_title": issuer_title,
"scope": scope,
"findings_summary": findings_summary,
"branding": branding.model_dump(),
"certificate_number": f"CERT-{assessment_date.strftime('%Y%m')}-{hash(client_name) % 10000:04d}"
}
document = generate_pdf_from_template(
template_id="compliance_certificate",
data=template_data,
options={
"paper_size": "letter",
"orientation": "landscape",
"include_qr_code": True, # QR code linking to certificate verification
"watermark": None
}
)
if output_path:
save_pdf(document, output_path)
return documentStep 4: Build a FastAPI Report Generation Service
from fastapi import FastAPI, HTTPException, BackgroundTasks
from fastapi.responses import Response
import io
app = FastAPI(title="Report Generation Service")
@app.post("/reports/analytics", response_class=Response)
async def create_analytics_report(report_data: MonthlyAnalyticsReport):
"""Generate a monthly analytics report and return it as a PDF download."""
document = generate_analytics_report(report_data)
return Response(
content=document.pdf_bytes,
media_type="application/pdf",
headers={
"Content-Disposition": f"attachment; filename={report_data.period_label.replace(' ', '_')}_report.pdf",
"X-Page-Count": str(document.page_count),
"X-Generation-Time-Ms": str(document.generation_time_ms)
}
)
@app.post("/reports/certificate")
async def create_compliance_cert(
client_name: str,
compliance_standard: str,
issuer_name: str,
issuer_title: str,
scope: str,
company_name: str
):
"""Generate a compliance certificate."""
branding = BrandingConfig(company_name=company_name)
document = generate_compliance_certificate(
client_name=client_name,
compliance_standard=compliance_standard,
assessment_date=date.today(),
valid_until=date(date.today().year + 1, date.today().month, date.today().day),
issuer_name=issuer_name,
issuer_title=issuer_title,
scope=scope,
findings_summary="Assessment completed with no critical findings.",
branding=branding
)
return Response(
content=document.pdf_bytes,
media_type="application/pdf",
headers={
"Content-Disposition": f"attachment; filename={client_name.replace(' ', '_')}_certificate.pdf"
}
)Step 5: Automate Monthly Report Distribution
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email.mime.text import MIMEText
from email import encoders
from datetime import date
def send_report_email(
recipient_email: str,
recipient_name: str,
report_document: GeneratedDocument,
report_filename: str,
sender_name: str,
sender_email: str,
smtp_config: dict
):
"""Email a generated PDF report to a recipient."""
msg = MIMEMultipart()
msg["From"] = f"{sender_name} <{sender_email}>"
msg["To"] = recipient_email
msg["Subject"] = f"Your {date.today().strftime('%B %Y')} Report"
body = f"""
Hi {recipient_name},
Please find your {date.today().strftime('%B %Y')} report attached.
This report was generated automatically. Please reach out if you have questions.
Best regards,
{sender_name}
"""
msg.attach(MIMEText(body, "plain"))
attachment = MIMEBase("application", "pdf")
attachment.set_payload(report_document.pdf_bytes)
encoders.encode_base64(attachment)
attachment.add_header(
"Content-Disposition",
"attachment",
filename=report_filename
)
msg.attach(attachment)
with smtplib.SMTP(smtp_config["host"], smtp_config["port"]) as server:
server.starttls()
server.login(smtp_config["username"], smtp_config["password"])
server.send_message(msg)
print(f"Report sent to {recipient_email}")Example: Generate a Real Report
# Build a sample analytics report
report = MonthlyAnalyticsReport(
report_title="Q1 2026 Performance Report",
report_subtitle="Executive Summary",
report_date=date(2026, 4, 1),
period_label="Q1 2026",
prepared_for="Acme Corporation",
prepared_by="Analytics Team",
branding=BrandingConfig(
company_name="Your Company",
primary_color="#2563eb"
),
executive_summary=(
"Q1 2026 delivered strong results across all key metrics, "
"with revenue up 34% YoY and customer acquisition costs down 18%."
),
key_metrics=[
MetricItem(label="Monthly Recurring Revenue", value=485000, unit="USD", change_pct=34.2, trend="up"),
MetricItem(label="Active Users", value=12847, unit="users", change_pct=22.1, trend="up"),
MetricItem(label="Churn Rate", value=2.1, unit="%", change_pct=-15.0, trend="down"),
MetricItem(label="NPS Score", value=67, unit="", change_pct=8.0, trend="up"),
],
sections=[
ReportSection(
heading="Revenue Performance",
body="Revenue grew 34.2% year-over-year, driven by expansion in enterprise accounts...",
metrics=[
MetricItem(label="New ARR", value=142000, unit="USD"),
MetricItem(label="Expansion ARR", value=87000, unit="USD"),
]
)
]
)
# Generate the PDF
document = generate_analytics_report(report, output_path="q1_2026_report.pdf")
print(f"Generated: {document.page_count} pages in {document.generation_time_ms}ms")Performance Benchmarks
In production with the DocForge API:
- Single-page certificate: ~400ms generation time
- 10-page analytics report: ~1.8 seconds
- Batch of 50 reports: ~45 seconds with parallel calls
- File sizes: typically 150-600 KB depending on charts and images
For context, a human generating the same report manually takes 45-90 minutes. The API delivers a 1500-3000x speed improvement.
Getting Started
The DocForge API is available at apivult.com. Sign up to access the template library and start generating PDFs from your data immediately.
The free tier includes 25 document generations per month. Pro tier unlocks unlimited generation, custom templates, white-label branding, and cloud storage integration for automatic report archiving.
More Articles
Generate Professional PDF Documents from Templates Using an API
Stop writing PDF generation code. Learn how to use the DocForge API to generate invoices, reports, contracts, and certificates from templates in seconds.
March 30, 2026
Auto-Generate Compliance Reports and Audit Certificates with an API
Learn how to use the DocForge API to automatically generate branded compliance reports, audit certificates, and regulatory documentation from structured data in Python.
March 31, 2026