Skip to content

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.

  1. Python 3.8 or later
  2. A Glyph API key - get one free

Store your API key in an environment variable:

Terminal window
# .env or shell profile
export GLYPH_API_KEY="gk_your_api_key"

For Django or FastAPI projects, use a .env file with python-dotenv:

Terminal window
pip install python-dotenv
# settings.py or config.py
from dotenv import load_dotenv
import os
load_dotenv()
GLYPH_API_KEY = os.getenv("GLYPH_API_KEY")

The simplest way to generate PDFs from Python is with the requests library.

Terminal window
pip install requests
import os
import base64
import 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"],
}
# Usage
if __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']}")

For interactive editing workflows, use the session-based API:

import os
import 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
# Usage
if __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)")

For high-throughput applications or async frameworks like FastAPI, use aiohttp.

Terminal window
pip install aiohttp
import os
import base64
import asyncio
import 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())

Generate multiple PDFs concurrently:

import asyncio
import aiohttp
import os
import 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())

Generate PDFs from Django views or management commands.

Add your API key to Django settings:

settings.py
import os
from dotenv import load_dotenv
load_dotenv()
GLYPH_API_KEY = os.getenv("GLYPH_API_KEY")

Create a reusable service:

invoices/services.py
import base64
import requests
from 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 instance
glyph_service = GlyphService()
invoices/views.py
from django.http import HttpResponse, Http404
from django.views import View
from .models import Invoice
from .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 response
invoices/urls.py
from django.urls import path
from .views import InvoicePDFView
urlpatterns = [
path("invoices/<int:invoice_id>/pdf/", InvoicePDFView.as_view(), name="invoice-pdf"),
]
invoices/management/commands/generate_invoices.py
from django.core.management.base import BaseCommand
from invoices.models import Invoice
from 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 works well with async Glyph calls.

Terminal window
pip install fastapi uvicorn aiohttp python-dotenv
main.py
import os
import base64
from typing import Optional
from contextlib import asynccontextmanager
import aiohttp
from fastapi import FastAPI, HTTPException, Response
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from dotenv import load_dotenv
load_dotenv()
GLYPH_API_KEY = os.getenv("GLYPH_API_KEY")
API_BASE = "https://api.glyph.you"
# Shared aiohttp session
class AppState:
session: aiohttp.ClientSession = None
state = AppState()
@asynccontextmanager
async 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 models
class 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"}
Terminal window
uvicorn main:app --reload
Terminal window
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}
]
}'

Handle common API errors gracefully:

import requests
from 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}")
# Usage
try:
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}")

Now that you can generate PDFs from Python:

  1. Explore templates: Browse available layouts
  2. Learn AI modifications: Natural language editing
  3. Batch processing: Generate multiple PDFs
  4. Webhook integration: Receive completion events
  5. MCP Server: Use with Claude and AI agents