Python

The Transactional client API is a single POST. The standard library handles it without a dependency. httpx is the modern pick if you want async + retries.

Standard library only (urllib)

import json
import os
import urllib.request
import urllib.error

def generate_pdf(document_id: str, variables: dict) -> dict:
    payload = json.dumps({
        "documentId": document_id,
        "variables": variables,
    }).encode()

    req = urllib.request.Request(
        "https://api.transactional.dev/v1/generate",
        method="POST",
        headers={
            "x-api-token": os.environ["TRANSACTIONAL_API_TOKEN"],
            "Content-Type": "application/json",
        },
        data=payload,
    )

    try:
        with urllib.request.urlopen(req, timeout=30) as resp:
            return json.loads(resp.read())
    except urllib.error.HTTPError as e:
        body = json.loads(e.read() or b"{}")
        raise RuntimeError(
            f"Transactional {e.code}: {body.get('error', 'unknown')} — {body.get('message', '')}"
        ) from e


result = generate_pdf(
    "1d8e5d56-1a4f-4b62-8c33-2d34a64b2f00",
    {
        "customer": {"name": "Acme Corp"},
        "invoice": {"number": "INV-2026-0142", "total": 1280.50},
    },
)
print(result["url"])

httpx with retries (sync + async)

import os
import httpx

TRANSACTIONAL_URL = "https://api.transactional.dev/v1/generate"

transport = httpx.HTTPTransport(retries=2)

def generate_pdf(document_id: str, variables: dict) -> dict:
    with httpx.Client(transport=transport, timeout=30) as client:
        r = client.post(
            TRANSACTIONAL_URL,
            json={"documentId": document_id, "variables": variables},
            headers={"x-api-token": os.environ["TRANSACTIONAL_API_TOKEN"]},
        )
    if r.is_success:
        return r.json()
    body = r.json() if r.headers.get("content-type", "").startswith("application/json") else {}
    raise TransactionalError(r.status_code, body.get("error", "unknown"), body.get("message", ""))


class TransactionalError(Exception):
    def __init__(self, status: int, code: str, message: str) -> None:
        super().__init__(f"Transactional {status}: {code} — {message}")
        self.status = status
        self.code = code

Async version (same shape):

async def generate_pdf_async(document_id: str, variables: dict) -> dict:
    async with httpx.AsyncClient(timeout=30) as client:
        r = await client.post(
            TRANSACTIONAL_URL,
            json={"documentId": document_id, "variables": variables},
            headers={"x-api-token": os.environ["TRANSACTIONAL_API_TOKEN"]},
        )
    r.raise_for_status()
    return r.json()

Branching on error codes

try:
    result = generate_pdf(uuid, variables)
except TransactionalError as e:
    if e.code == "quota_exceeded":
        raise OutOfCredits()
    if e.code in ("NOT_FOUND", "invalid_document_id"):
        raise BadTemplate(uuid)
    if e.code == "UNAUTHORIZED":
        raise ApiTokenInvalid()
    raise

Django integration

# settings.py
TRANSACTIONAL_API_TOKEN = os.environ["TRANSACTIONAL_API_TOKEN"]
TRANSACTIONAL_INVOICE_TEMPLATE = os.environ["TRANSACTIONAL_INVOICE_TEMPLATE_UUID"]

# A view that streams the PDF inline as the response
from django.http import HttpResponse
from django.conf import settings
import httpx

def download_invoice(request, invoice_id):
    invoice = get_object_or_404(Invoice, id=invoice_id, user=request.user)

    res = httpx.post(
        "https://api.transactional.dev/v1/generate",
        json={
            "documentId": settings.TRANSACTIONAL_INVOICE_TEMPLATE,
            "variables": invoice.template_variables(),
        },
        headers={"x-api-token": settings.TRANSACTIONAL_API_TOKEN},
        timeout=30,
    )
    res.raise_for_status()

    pdf = httpx.get(res.json()["url"], timeout=30).content
    response = HttpResponse(pdf, content_type="application/pdf")
    response["Content-Disposition"] = f'attachment; filename="invoice-{invoice.number}.pdf"'
    return response

For background generation, push to Celery:

@shared_task(bind=True, autoretry_for=(httpx.HTTPError,), max_retries=3, retry_backoff=True)
def generate_invoice_pdf(self, invoice_id):
    invoice = Invoice.objects.get(id=invoice_id)
    # ... same as above, then save to S3 / Django storages

FastAPI integration

from fastapi import FastAPI, HTTPException
import httpx
import os

app = FastAPI()

@app.post("/invoices/{id}/pdf")
async def render_invoice(id: int):
    invoice = await Invoice.get(id)
    async with httpx.AsyncClient(timeout=30) as client:
        res = await client.post(
            "https://api.transactional.dev/v1/generate",
            json={
                "documentId": os.environ["TRANSACTIONAL_INVOICE_TEMPLATE_UUID"],
                "variables": invoice.to_template_vars(),
            },
            headers={"x-api-token": os.environ["TRANSACTIONAL_API_TOKEN"]},
        )
    if res.is_error:
        body = res.json()
        raise HTTPException(res.status_code, detail=body)
    return res.json()  # {url, documentId}

Next steps