Python Quickstart
This guide covers everything you need to generate PDFs from Python using the Glyph REST API. Whether you are building a Django app, a FastAPI service, or a simple script, you will find working examples here.
Prerequisites
Section titled “Prerequisites”- Python 3.8 or later
- A Glyph API key - get one free
Environment Setup
Section titled “Environment Setup”Store your API key in an environment variable:
# .env or shell profileexport GLYPH_API_KEY="gk_your_api_key"For Django or FastAPI projects, use a .env file with python-dotenv:
pip install python-dotenv# settings.py or config.pyfrom dotenv import load_dotenvimport os
load_dotenv()GLYPH_API_KEY = os.getenv("GLYPH_API_KEY")Synchronous Usage with requests
Section titled “Synchronous Usage with requests”The simplest way to generate PDFs from Python is with the requests library.
Installation
Section titled “Installation”pip install requestsBasic Example
Section titled “Basic Example”import osimport base64import requests
GLYPH_API_KEY = os.getenv("GLYPH_API_KEY")API_BASE = "https://api.glyph.you"
def create_invoice_pdf(invoice_data: dict, output_path: str = "invoice.pdf") -> dict: """Generate an invoice PDF from structured data.""" response = requests.post( f"{API_BASE}/v1/create", headers={ "Authorization": f"Bearer {GLYPH_API_KEY}", "Content-Type": "application/json", "Accept": "application/json", }, json={ "data": invoice_data, "intent": "professional invoice", "style": "stripe-clean", }, ) response.raise_for_status() result = response.json()
# Decode and save the PDF data_url = result["url"] base64_data = data_url.split(",")[1] with open(output_path, "wb") as f: f.write(base64.b64decode(base64_data))
return { "file": output_path, "size": result["size"], "detected_type": result["analysis"]["detectedType"], "session_id": result["sessionId"], }
# Usageif __name__ == "__main__": invoice = { "company": {"name": "Acme Inc", "email": "billing@acme.com"}, "customer": {"name": "John Doe", "email": "john@example.com"}, "items": [ {"description": "Consulting", "hours": 10, "rate": 150, "total": 1500}, {"description": "Development", "hours": 20, "rate": 125, "total": 2500}, ], "subtotal": 4000, "tax": 320, "total": 4320, "invoiceNumber": "INV-2026-001", "date": "2026-01-15", "dueDate": "2026-02-15", }
result = create_invoice_pdf(invoice) print(f"Created {result['file']} ({result['size']} bytes)") print(f"Detected as: {result['detected_type']}")Full Workflow: Preview, Modify, Generate
Section titled “Full Workflow: Preview, Modify, Generate”For interactive editing workflows, use the session-based API:
import osimport requests
GLYPH_API_KEY = os.getenv("GLYPH_API_KEY")API_BASE = "https://api.glyph.you"
class GlyphClient: """Simple sync client for Glyph API."""
def __init__(self, api_key: str = None): self.api_key = api_key or os.getenv("GLYPH_API_KEY") self.headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", }
def preview(self, template: str, data: dict) -> dict: """Create a preview session.""" response = requests.post( f"{API_BASE}/v1/preview", headers=self.headers, json={"template": template, "data": data}, ) response.raise_for_status() return response.json()
def modify(self, session_id: str, prompt: str, region: str = None) -> dict: """Apply AI modification to the document.""" payload = {"sessionId": session_id, "prompt": prompt} if region: payload["region"] = region
response = requests.post( f"{API_BASE}/v1/modify", headers=self.headers, json=payload, ) response.raise_for_status() return response.json()
def generate(self, session_id: str, format: str = "pdf") -> bytes: """Generate the final PDF/PNG.""" response = requests.post( f"{API_BASE}/v1/generate", headers=self.headers, json={"sessionId": session_id, "format": format}, ) response.raise_for_status() return response.content
# Usageif __name__ == "__main__": client = GlyphClient()
# Step 1: Create preview data = { "client": {"name": "Acme Corp", "email": "contact@acme.com"}, "lineItems": [ {"description": "Website Design", "quantity": 1, "unitPrice": 5000, "total": 5000} ], "totals": {"subtotal": 5000, "tax": 400, "total": 5400}, "meta": {"quoteNumber": "Q-2026-042", "date": "January 28, 2026"}, } preview = client.preview("quote-modern", data) print(f"Session ID: {preview['sessionId']}")
# Step 2: Apply modifications client.modify(preview["sessionId"], "Make the header navy blue", region="header") client.modify(preview["sessionId"], "Add a 10% discount line", region="totals")
# Step 3: Generate final PDF pdf_bytes = client.generate(preview["sessionId"]) with open("quote.pdf", "wb") as f: f.write(pdf_bytes) print(f"Generated quote.pdf ({len(pdf_bytes)} bytes)")Asynchronous Usage with aiohttp
Section titled “Asynchronous Usage with aiohttp”For high-throughput applications or async frameworks like FastAPI, use aiohttp.
Installation
Section titled “Installation”pip install aiohttpAsync Client
Section titled “Async Client”import osimport base64import asyncioimport aiohttp
GLYPH_API_KEY = os.getenv("GLYPH_API_KEY")API_BASE = "https://api.glyph.you"
class AsyncGlyphClient: """Async client for Glyph API."""
def __init__(self, api_key: str = None): self.api_key = api_key or os.getenv("GLYPH_API_KEY") self.headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json", }
async def create( self, data: dict, intent: str = None, style: str = None, template_id: str = None, ) -> dict: """Create a PDF in one call.""" payload = {"data": data} if intent: payload["intent"] = intent if style: payload["style"] = style if template_id: payload["templateId"] = template_id
async with aiohttp.ClientSession() as session: async with session.post( f"{API_BASE}/v1/create", headers={**self.headers, "Accept": "application/json"}, json=payload, ) as response: response.raise_for_status() return await response.json()
async def preview(self, template: str, data: dict) -> dict: """Create a preview session.""" async with aiohttp.ClientSession() as session: async with session.post( f"{API_BASE}/v1/preview", headers=self.headers, json={"template": template, "data": data}, ) as response: response.raise_for_status() return await response.json()
async def modify(self, session_id: str, prompt: str, region: str = None) -> dict: """Apply AI modification.""" payload = {"sessionId": session_id, "prompt": prompt} if region: payload["region"] = region
async with aiohttp.ClientSession() as session: async with session.post( f"{API_BASE}/v1/modify", headers=self.headers, json=payload, ) as response: response.raise_for_status() return await response.json()
async def generate(self, session_id: str, format: str = "pdf") -> bytes: """Generate final document.""" async with aiohttp.ClientSession() as session: async with session.post( f"{API_BASE}/v1/generate", headers=self.headers, json={"sessionId": session_id, "format": format}, ) as response: response.raise_for_status() return await response.read()
async def main(): client = AsyncGlyphClient()
# Generate an invoice invoice_data = { "company": {"name": "Acme Inc"}, "customer": {"name": "Jane Doe", "email": "jane@example.com"}, "items": [{"description": "Annual License", "quantity": 1, "total": 999}], "total": 999, "invoiceNumber": "INV-2026-100", }
result = await client.create(invoice_data, intent="professional invoice") print(f"Detected: {result['analysis']['detectedType']}") print(f"Session: {result['sessionId']}")
# Save PDF data_url = result["url"] base64_data = data_url.split(",")[1] with open("invoice.pdf", "wb") as f: f.write(base64.b64decode(base64_data)) print("Saved invoice.pdf")
if __name__ == "__main__": asyncio.run(main())Batch Generation with Concurrency
Section titled “Batch Generation with Concurrency”Generate multiple PDFs concurrently:
import asyncioimport aiohttpimport osimport base64
GLYPH_API_KEY = os.getenv("GLYPH_API_KEY")API_BASE = "https://api.glyph.you"
async def generate_invoice(session: aiohttp.ClientSession, invoice: dict) -> dict: """Generate a single invoice PDF.""" headers = { "Authorization": f"Bearer {GLYPH_API_KEY}", "Content-Type": "application/json", "Accept": "application/json", }
async with session.post( f"{API_BASE}/v1/create", headers=headers, json={"data": invoice, "style": "stripe-clean"}, ) as response: response.raise_for_status() result = await response.json()
# Decode PDF data_url = result["url"] base64_data = data_url.split(",")[1] pdf_bytes = base64.b64decode(base64_data)
return { "invoice_number": invoice.get("invoiceNumber"), "pdf": pdf_bytes, "size": result["size"], }
async def batch_generate(invoices: list[dict], max_concurrent: int = 5) -> list[dict]: """Generate multiple invoices with concurrency limit.""" semaphore = asyncio.Semaphore(max_concurrent)
async def limited_generate(session, invoice): async with semaphore: return await generate_invoice(session, invoice)
async with aiohttp.ClientSession() as session: tasks = [limited_generate(session, inv) for inv in invoices] return await asyncio.gather(*tasks, return_exceptions=True)
async def main(): # Sample invoices invoices = [ { "company": {"name": "Acme Inc"}, "customer": {"name": f"Customer {i}"}, "items": [{"description": "Service", "total": 100 * i}], "total": 100 * i, "invoiceNumber": f"INV-2026-{i:03d}", } for i in range(1, 6) ]
results = await batch_generate(invoices)
for result in results: if isinstance(result, Exception): print(f"Error: {result}") else: filename = f"{result['invoice_number']}.pdf" with open(filename, "wb") as f: f.write(result["pdf"]) print(f"Generated {filename} ({result['size']} bytes)")
if __name__ == "__main__": asyncio.run(main())Django Integration
Section titled “Django Integration”Generate PDFs from Django views or management commands.
Add your API key to Django settings:
import osfrom dotenv import load_dotenv
load_dotenv()
GLYPH_API_KEY = os.getenv("GLYPH_API_KEY")Service Layer
Section titled “Service Layer”Create a reusable service:
import base64import requestsfrom django.conf import settings
class GlyphService: """Django service for generating PDFs with Glyph."""
API_BASE = "https://api.glyph.you"
def __init__(self): self.headers = { "Authorization": f"Bearer {settings.GLYPH_API_KEY}", "Content-Type": "application/json", }
def create_pdf( self, data: dict, intent: str = None, style: str = "stripe-clean", ) -> tuple[bytes, dict]: """ Generate a PDF from data.
Returns: Tuple of (pdf_bytes, metadata) """ payload = {"data": data} if intent: payload["intent"] = intent if style: payload["style"] = style
response = requests.post( f"{self.API_BASE}/v1/create", headers={**self.headers, "Accept": "application/json"}, json=payload, timeout=30, ) response.raise_for_status() result = response.json()
# Decode PDF from base64 data_url = result["url"] base64_data = data_url.split(",")[1] pdf_bytes = base64.b64decode(base64_data)
return pdf_bytes, { "size": result["size"], "detected_type": result["analysis"]["detectedType"], "session_id": result["sessionId"], }
def generate_invoice(self, invoice) -> tuple[bytes, dict]: """Generate PDF for an Invoice model instance.""" data = { "company": { "name": invoice.company.name, "email": invoice.company.email, "address": invoice.company.address, }, "customer": { "name": invoice.customer.name, "email": invoice.customer.email, }, "items": [ { "description": item.description, "quantity": item.quantity, "unitPrice": float(item.unit_price), "total": float(item.total), } for item in invoice.items.all() ], "subtotal": float(invoice.subtotal), "tax": float(invoice.tax), "total": float(invoice.total), "invoiceNumber": invoice.number, "date": invoice.date.isoformat(), "dueDate": invoice.due_date.isoformat(), } return self.create_pdf(data, intent="professional invoice")
# Singleton instanceglyph_service = GlyphService()View Example
Section titled “View Example”from django.http import HttpResponse, Http404from django.views import Viewfrom .models import Invoicefrom .services import glyph_service
class InvoicePDFView(View): """Download an invoice as PDF."""
def get(self, request, invoice_id): try: invoice = Invoice.objects.select_related("company", "customer").prefetch_related("items").get(id=invoice_id) except Invoice.DoesNotExist: raise Http404("Invoice not found")
# Generate PDF pdf_bytes, metadata = glyph_service.generate_invoice(invoice)
# Return as download response = HttpResponse(pdf_bytes, content_type="application/pdf") response["Content-Disposition"] = f'attachment; filename="{invoice.number}.pdf"' response["Content-Length"] = metadata["size"] return responseURL Configuration
Section titled “URL Configuration”from django.urls import pathfrom .views import InvoicePDFView
urlpatterns = [ path("invoices/<int:invoice_id>/pdf/", InvoicePDFView.as_view(), name="invoice-pdf"),]Management Command
Section titled “Management Command”from django.core.management.base import BaseCommandfrom invoices.models import Invoicefrom invoices.services import glyph_service
class Command(BaseCommand): help = "Generate PDFs for all unpaid invoices"
def handle(self, *args, **options): invoices = Invoice.objects.filter(status="unpaid") self.stdout.write(f"Generating PDFs for {invoices.count()} invoices...")
for invoice in invoices: try: pdf_bytes, metadata = glyph_service.generate_invoice(invoice) filename = f"invoices/{invoice.number}.pdf"
# Save to file or upload to S3 with open(filename, "wb") as f: f.write(pdf_bytes)
self.stdout.write(self.style.SUCCESS(f"Generated {filename}")) except Exception as e: self.stderr.write(self.style.ERROR(f"Failed {invoice.number}: {e}"))FastAPI Integration
Section titled “FastAPI Integration”FastAPI works well with async Glyph calls.
pip install fastapi uvicorn aiohttp python-dotenvApplication
Section titled “Application”import osimport base64from typing import Optionalfrom contextlib import asynccontextmanager
import aiohttpfrom fastapi import FastAPI, HTTPException, Responsefrom fastapi.responses import StreamingResponsefrom pydantic import BaseModelfrom dotenv import load_dotenv
load_dotenv()
GLYPH_API_KEY = os.getenv("GLYPH_API_KEY")API_BASE = "https://api.glyph.you"
# Shared aiohttp sessionclass AppState: session: aiohttp.ClientSession = None
state = AppState()
@asynccontextmanagerasync def lifespan(app: FastAPI): """Manage aiohttp session lifecycle.""" state.session = aiohttp.ClientSession() yield await state.session.close()
app = FastAPI(title="Invoice API", lifespan=lifespan)
# Request/Response modelsclass LineItem(BaseModel): description: str quantity: int = 1 unit_price: float total: float
class InvoiceRequest(BaseModel): company_name: str customer_name: str customer_email: str items: list[LineItem] tax_rate: float = 0.08 invoice_number: Optional[str] = None style: str = "stripe-clean"
class InvoiceResponse(BaseModel): invoice_number: str total: float pdf_url: str session_id: str
@app.post("/invoices/generate", response_model=InvoiceResponse)async def generate_invoice(request: InvoiceRequest): """Generate an invoice PDF and return metadata.""" # Calculate totals subtotal = sum(item.total for item in request.items) tax = subtotal * request.tax_rate total = subtotal + tax
# Build Glyph payload data = { "company": {"name": request.company_name}, "customer": {"name": request.customer_name, "email": request.customer_email}, "items": [ { "description": item.description, "quantity": item.quantity, "unitPrice": item.unit_price, "total": item.total, } for item in request.items ], "subtotal": subtotal, "tax": tax, "total": total, "invoiceNumber": request.invoice_number or f"INV-{os.urandom(4).hex().upper()}", }
headers = { "Authorization": f"Bearer {GLYPH_API_KEY}", "Content-Type": "application/json", "Accept": "application/json", }
async with state.session.post( f"{API_BASE}/v1/create", headers=headers, json={"data": data, "style": request.style}, ) as response: if response.status != 200: error = await response.text() raise HTTPException(status_code=response.status, detail=error)
result = await response.json()
return InvoiceResponse( invoice_number=data["invoiceNumber"], total=total, pdf_url=result["url"], session_id=result["sessionId"], )
@app.post("/invoices/download")async def download_invoice(request: InvoiceRequest): """Generate and return the PDF directly.""" # Calculate totals subtotal = sum(item.total for item in request.items) tax = subtotal * request.tax_rate total = subtotal + tax
data = { "company": {"name": request.company_name}, "customer": {"name": request.customer_name, "email": request.customer_email}, "items": [ { "description": item.description, "quantity": item.quantity, "unitPrice": item.unit_price, "total": item.total, } for item in request.items ], "subtotal": subtotal, "tax": tax, "total": total, "invoiceNumber": request.invoice_number or f"INV-{os.urandom(4).hex().upper()}", }
headers = { "Authorization": f"Bearer {GLYPH_API_KEY}", "Content-Type": "application/json", "Accept": "application/json", }
async with state.session.post( f"{API_BASE}/v1/create", headers=headers, json={"data": data, "style": request.style}, ) as response: if response.status != 200: error = await response.text() raise HTTPException(status_code=response.status, detail=error)
result = await response.json()
# Decode base64 PDF data_url = result["url"] base64_data = data_url.split(",")[1] pdf_bytes = base64.b64decode(base64_data)
return Response( content=pdf_bytes, media_type="application/pdf", headers={ "Content-Disposition": f'attachment; filename="{data["invoiceNumber"]}.pdf"' }, )
@app.get("/health")async def health(): """Health check.""" return {"status": "ok"}Running the Server
Section titled “Running the Server”uvicorn main:app --reloadTesting
Section titled “Testing”curl -X POST http://localhost:8000/invoices/generate \ -H "Content-Type: application/json" \ -d '{ "company_name": "Acme Inc", "customer_name": "John Doe", "customer_email": "john@example.com", "items": [ {"description": "Consulting", "quantity": 10, "unit_price": 150, "total": 1500} ] }'Error Handling
Section titled “Error Handling”Handle common API errors gracefully:
import requestsfrom requests.exceptions import RequestException
class GlyphError(Exception): """Base exception for Glyph API errors."""
def __init__(self, message: str, code: str = None, status: int = None): super().__init__(message) self.code = code self.status = status
class GlyphAuthError(GlyphError): """Authentication failed.""" pass
class GlyphRateLimitError(GlyphError): """Rate limit exceeded.""" pass
class GlyphValidationError(GlyphError): """Request validation failed.""" pass
def call_glyph_api(method: str, endpoint: str, **kwargs) -> dict: """Make an API call with error handling.""" try: response = requests.request(method, f"https://api.glyph.you{endpoint}", **kwargs)
# Check for errors if response.status_code == 401: raise GlyphAuthError("Invalid API key", status=401) elif response.status_code == 429: data = response.json() raise GlyphRateLimitError( f"Rate limit exceeded. Limit: {data.get('limit')}, Used: {data.get('used')}", code="RATE_LIMIT_EXCEEDED", status=429, ) elif response.status_code == 400: data = response.json() raise GlyphValidationError( data.get("error", "Validation failed"), code=data.get("code"), status=400, ) elif not response.ok: data = response.json() if response.headers.get("content-type", "").startswith("application/json") else {} raise GlyphError( data.get("error", f"HTTP {response.status_code}"), code=data.get("code"), status=response.status_code, )
return response.json()
except RequestException as e: raise GlyphError(f"Network error: {e}")
# Usagetry: result = call_glyph_api( "POST", "/v1/create", headers={ "Authorization": f"Bearer {GLYPH_API_KEY}", "Content-Type": "application/json", "Accept": "application/json", }, json={"data": invoice_data}, )except GlyphAuthError: print("Check your API key")except GlyphRateLimitError as e: print(f"Slow down: {e}")except GlyphValidationError as e: print(f"Invalid request: {e}")except GlyphError as e: print(f"API error: {e}")Next Steps
Section titled “Next Steps”Now that you can generate PDFs from Python:
- Explore templates: Browse available layouts
- Learn AI modifications: Natural language editing
- Batch processing: Generate multiple PDFs
- Webhook integration: Receive completion events
- MCP Server: Use with Claude and AI agents