[{"data":1,"prerenderedAt":920},["ShallowReactive",2],{"guide-\u002Fdocs\u002Fguides\u002Fintegrations\u002Fpython":3,"guides-all":852},{"id":4,"title":5,"body":6,"category":843,"description":844,"extension":845,"icon":846,"meta":847,"navigation":67,"order":52,"path":848,"seo":849,"stem":850,"__hash__":851},"guides\u002Fdocs\u002Fguides\u002Fintegrations\u002Fpython.md","Calling \u002Fv1\u002Fgenerate from Python",{"type":7,"value":8,"toc":835},"minimark",[9,14,23,28,269,273,396,399,445,449,504,508,640,643,668,672,780,806,810,831],[10,11,13],"h1",{"id":12},"python","Python",[15,16,17,18,22],"p",{},"The Transactional client API is a single POST. The standard library handles it without a dependency. ",[19,20,21],"code",{},"httpx"," is the modern pick if you want async + retries.",[24,25,27],"h2",{"id":26},"standard-library-only-urllib","Standard library only (urllib)",[29,30,34],"pre",{"className":31,"code":32,"language":12,"meta":33,"style":33},"language-python shiki shiki-themes github-light github-dark","import json\nimport os\nimport urllib.request\nimport urllib.error\n\ndef generate_pdf(document_id: str, variables: dict) -> dict:\n    payload = json.dumps({\n        \"documentId\": document_id,\n        \"variables\": variables,\n    }).encode()\n\n    req = urllib.request.Request(\n        \"https:\u002F\u002Fapi.transactional.dev\u002Fv1\u002Fgenerate\",\n        method=\"POST\",\n        headers={\n            \"x-api-token\": os.environ[\"TRANSACTIONAL_API_TOKEN\"],\n            \"Content-Type\": \"application\u002Fjson\",\n        },\n        data=payload,\n    )\n\n    try:\n        with urllib.request.urlopen(req, timeout=30) as resp:\n            return json.loads(resp.read())\n    except urllib.error.HTTPError as e:\n        body = json.loads(e.read() or b\"{}\")\n        raise RuntimeError(\n            f\"Transactional {e.code}: {body.get('error', 'unknown')} — {body.get('message', '')}\"\n        ) from e\n\n\nresult = generate_pdf(\n    \"1d8e5d56-1a4f-4b62-8c33-2d34a64b2f00\",\n    {\n        \"customer\": {\"name\": \"Acme Corp\"},\n        \"invoice\": {\"number\": \"INV-2026-0142\", \"total\": 1280.50},\n    },\n)\nprint(result[\"url\"])\n","",[19,35,36,44,50,56,62,69,75,81,87,93,99,104,110,116,122,128,134,140,146,152,158,163,169,175,181,187,193,199,205,211,216,221,227,233,239,245,251,257,263],{"__ignoreMap":33},[37,38,41],"span",{"class":39,"line":40},"line",1,[37,42,43],{},"import json\n",[37,45,47],{"class":39,"line":46},2,[37,48,49],{},"import os\n",[37,51,53],{"class":39,"line":52},3,[37,54,55],{},"import urllib.request\n",[37,57,59],{"class":39,"line":58},4,[37,60,61],{},"import urllib.error\n",[37,63,65],{"class":39,"line":64},5,[37,66,68],{"emptyLinePlaceholder":67},true,"\n",[37,70,72],{"class":39,"line":71},6,[37,73,74],{},"def generate_pdf(document_id: str, variables: dict) -> dict:\n",[37,76,78],{"class":39,"line":77},7,[37,79,80],{},"    payload = json.dumps({\n",[37,82,84],{"class":39,"line":83},8,[37,85,86],{},"        \"documentId\": document_id,\n",[37,88,90],{"class":39,"line":89},9,[37,91,92],{},"        \"variables\": variables,\n",[37,94,96],{"class":39,"line":95},10,[37,97,98],{},"    }).encode()\n",[37,100,102],{"class":39,"line":101},11,[37,103,68],{"emptyLinePlaceholder":67},[37,105,107],{"class":39,"line":106},12,[37,108,109],{},"    req = urllib.request.Request(\n",[37,111,113],{"class":39,"line":112},13,[37,114,115],{},"        \"https:\u002F\u002Fapi.transactional.dev\u002Fv1\u002Fgenerate\",\n",[37,117,119],{"class":39,"line":118},14,[37,120,121],{},"        method=\"POST\",\n",[37,123,125],{"class":39,"line":124},15,[37,126,127],{},"        headers={\n",[37,129,131],{"class":39,"line":130},16,[37,132,133],{},"            \"x-api-token\": os.environ[\"TRANSACTIONAL_API_TOKEN\"],\n",[37,135,137],{"class":39,"line":136},17,[37,138,139],{},"            \"Content-Type\": \"application\u002Fjson\",\n",[37,141,143],{"class":39,"line":142},18,[37,144,145],{},"        },\n",[37,147,149],{"class":39,"line":148},19,[37,150,151],{},"        data=payload,\n",[37,153,155],{"class":39,"line":154},20,[37,156,157],{},"    )\n",[37,159,161],{"class":39,"line":160},21,[37,162,68],{"emptyLinePlaceholder":67},[37,164,166],{"class":39,"line":165},22,[37,167,168],{},"    try:\n",[37,170,172],{"class":39,"line":171},23,[37,173,174],{},"        with urllib.request.urlopen(req, timeout=30) as resp:\n",[37,176,178],{"class":39,"line":177},24,[37,179,180],{},"            return json.loads(resp.read())\n",[37,182,184],{"class":39,"line":183},25,[37,185,186],{},"    except urllib.error.HTTPError as e:\n",[37,188,190],{"class":39,"line":189},26,[37,191,192],{},"        body = json.loads(e.read() or b\"{}\")\n",[37,194,196],{"class":39,"line":195},27,[37,197,198],{},"        raise RuntimeError(\n",[37,200,202],{"class":39,"line":201},28,[37,203,204],{},"            f\"Transactional {e.code}: {body.get('error', 'unknown')} — {body.get('message', '')}\"\n",[37,206,208],{"class":39,"line":207},29,[37,209,210],{},"        ) from e\n",[37,212,214],{"class":39,"line":213},30,[37,215,68],{"emptyLinePlaceholder":67},[37,217,219],{"class":39,"line":218},31,[37,220,68],{"emptyLinePlaceholder":67},[37,222,224],{"class":39,"line":223},32,[37,225,226],{},"result = generate_pdf(\n",[37,228,230],{"class":39,"line":229},33,[37,231,232],{},"    \"1d8e5d56-1a4f-4b62-8c33-2d34a64b2f00\",\n",[37,234,236],{"class":39,"line":235},34,[37,237,238],{},"    {\n",[37,240,242],{"class":39,"line":241},35,[37,243,244],{},"        \"customer\": {\"name\": \"Acme Corp\"},\n",[37,246,248],{"class":39,"line":247},36,[37,249,250],{},"        \"invoice\": {\"number\": \"INV-2026-0142\", \"total\": 1280.50},\n",[37,252,254],{"class":39,"line":253},37,[37,255,256],{},"    },\n",[37,258,260],{"class":39,"line":259},38,[37,261,262],{},")\n",[37,264,266],{"class":39,"line":265},39,[37,267,268],{},"print(result[\"url\"])\n",[24,270,272],{"id":271},"httpx-with-retries-sync-async","httpx with retries (sync + async)",[29,274,276],{"className":31,"code":275,"language":12,"meta":33,"style":33},"import os\nimport httpx\n\nTRANSACTIONAL_URL = \"https:\u002F\u002Fapi.transactional.dev\u002Fv1\u002Fgenerate\"\n\ntransport = httpx.HTTPTransport(retries=2)\n\ndef generate_pdf(document_id: str, variables: dict) -> dict:\n    with httpx.Client(transport=transport, timeout=30) as client:\n        r = client.post(\n            TRANSACTIONAL_URL,\n            json={\"documentId\": document_id, \"variables\": variables},\n            headers={\"x-api-token\": os.environ[\"TRANSACTIONAL_API_TOKEN\"]},\n        )\n    if r.is_success:\n        return r.json()\n    body = r.json() if r.headers.get(\"content-type\", \"\").startswith(\"application\u002Fjson\") else {}\n    raise TransactionalError(r.status_code, body.get(\"error\", \"unknown\"), body.get(\"message\", \"\"))\n\n\nclass TransactionalError(Exception):\n    def __init__(self, status: int, code: str, message: str) -> None:\n        super().__init__(f\"Transactional {status}: {code} — {message}\")\n        self.status = status\n        self.code = code\n",[19,277,278,282,287,291,296,300,305,309,313,318,323,328,333,338,343,348,353,358,363,367,371,376,381,386,391],{"__ignoreMap":33},[37,279,280],{"class":39,"line":40},[37,281,49],{},[37,283,284],{"class":39,"line":46},[37,285,286],{},"import httpx\n",[37,288,289],{"class":39,"line":52},[37,290,68],{"emptyLinePlaceholder":67},[37,292,293],{"class":39,"line":58},[37,294,295],{},"TRANSACTIONAL_URL = \"https:\u002F\u002Fapi.transactional.dev\u002Fv1\u002Fgenerate\"\n",[37,297,298],{"class":39,"line":64},[37,299,68],{"emptyLinePlaceholder":67},[37,301,302],{"class":39,"line":71},[37,303,304],{},"transport = httpx.HTTPTransport(retries=2)\n",[37,306,307],{"class":39,"line":77},[37,308,68],{"emptyLinePlaceholder":67},[37,310,311],{"class":39,"line":83},[37,312,74],{},[37,314,315],{"class":39,"line":89},[37,316,317],{},"    with httpx.Client(transport=transport, timeout=30) as client:\n",[37,319,320],{"class":39,"line":95},[37,321,322],{},"        r = client.post(\n",[37,324,325],{"class":39,"line":101},[37,326,327],{},"            TRANSACTIONAL_URL,\n",[37,329,330],{"class":39,"line":106},[37,331,332],{},"            json={\"documentId\": document_id, \"variables\": variables},\n",[37,334,335],{"class":39,"line":112},[37,336,337],{},"            headers={\"x-api-token\": os.environ[\"TRANSACTIONAL_API_TOKEN\"]},\n",[37,339,340],{"class":39,"line":118},[37,341,342],{},"        )\n",[37,344,345],{"class":39,"line":124},[37,346,347],{},"    if r.is_success:\n",[37,349,350],{"class":39,"line":130},[37,351,352],{},"        return r.json()\n",[37,354,355],{"class":39,"line":136},[37,356,357],{},"    body = r.json() if r.headers.get(\"content-type\", \"\").startswith(\"application\u002Fjson\") else {}\n",[37,359,360],{"class":39,"line":142},[37,361,362],{},"    raise TransactionalError(r.status_code, body.get(\"error\", \"unknown\"), body.get(\"message\", \"\"))\n",[37,364,365],{"class":39,"line":148},[37,366,68],{"emptyLinePlaceholder":67},[37,368,369],{"class":39,"line":154},[37,370,68],{"emptyLinePlaceholder":67},[37,372,373],{"class":39,"line":160},[37,374,375],{},"class TransactionalError(Exception):\n",[37,377,378],{"class":39,"line":165},[37,379,380],{},"    def __init__(self, status: int, code: str, message: str) -> None:\n",[37,382,383],{"class":39,"line":171},[37,384,385],{},"        super().__init__(f\"Transactional {status}: {code} — {message}\")\n",[37,387,388],{"class":39,"line":177},[37,389,390],{},"        self.status = status\n",[37,392,393],{"class":39,"line":183},[37,394,395],{},"        self.code = code\n",[15,397,398],{},"Async version (same shape):",[29,400,402],{"className":31,"code":401,"language":12,"meta":33,"style":33},"async def generate_pdf_async(document_id: str, variables: dict) -> dict:\n    async with httpx.AsyncClient(timeout=30) as client:\n        r = await client.post(\n            TRANSACTIONAL_URL,\n            json={\"documentId\": document_id, \"variables\": variables},\n            headers={\"x-api-token\": os.environ[\"TRANSACTIONAL_API_TOKEN\"]},\n        )\n    r.raise_for_status()\n    return r.json()\n",[19,403,404,409,414,419,423,427,431,435,440],{"__ignoreMap":33},[37,405,406],{"class":39,"line":40},[37,407,408],{},"async def generate_pdf_async(document_id: str, variables: dict) -> dict:\n",[37,410,411],{"class":39,"line":46},[37,412,413],{},"    async with httpx.AsyncClient(timeout=30) as client:\n",[37,415,416],{"class":39,"line":52},[37,417,418],{},"        r = await client.post(\n",[37,420,421],{"class":39,"line":58},[37,422,327],{},[37,424,425],{"class":39,"line":64},[37,426,332],{},[37,428,429],{"class":39,"line":71},[37,430,337],{},[37,432,433],{"class":39,"line":77},[37,434,342],{},[37,436,437],{"class":39,"line":83},[37,438,439],{},"    r.raise_for_status()\n",[37,441,442],{"class":39,"line":89},[37,443,444],{},"    return r.json()\n",[24,446,448],{"id":447},"branching-on-error-codes","Branching on error codes",[29,450,452],{"className":31,"code":451,"language":12,"meta":33,"style":33},"try:\n    result = generate_pdf(uuid, variables)\nexcept TransactionalError as e:\n    if e.code == \"quota_exceeded\":\n        raise OutOfCredits()\n    if e.code in (\"NOT_FOUND\", \"invalid_document_id\"):\n        raise BadTemplate(uuid)\n    if e.code == \"UNAUTHORIZED\":\n        raise ApiTokenInvalid()\n    raise\n",[19,453,454,459,464,469,474,479,484,489,494,499],{"__ignoreMap":33},[37,455,456],{"class":39,"line":40},[37,457,458],{},"try:\n",[37,460,461],{"class":39,"line":46},[37,462,463],{},"    result = generate_pdf(uuid, variables)\n",[37,465,466],{"class":39,"line":52},[37,467,468],{},"except TransactionalError as e:\n",[37,470,471],{"class":39,"line":58},[37,472,473],{},"    if e.code == \"quota_exceeded\":\n",[37,475,476],{"class":39,"line":64},[37,477,478],{},"        raise OutOfCredits()\n",[37,480,481],{"class":39,"line":71},[37,482,483],{},"    if e.code in (\"NOT_FOUND\", \"invalid_document_id\"):\n",[37,485,486],{"class":39,"line":77},[37,487,488],{},"        raise BadTemplate(uuid)\n",[37,490,491],{"class":39,"line":83},[37,492,493],{},"    if e.code == \"UNAUTHORIZED\":\n",[37,495,496],{"class":39,"line":89},[37,497,498],{},"        raise ApiTokenInvalid()\n",[37,500,501],{"class":39,"line":95},[37,502,503],{},"    raise\n",[24,505,507],{"id":506},"django-integration","Django integration",[29,509,511],{"className":31,"code":510,"language":12,"meta":33,"style":33},"# settings.py\nTRANSACTIONAL_API_TOKEN = os.environ[\"TRANSACTIONAL_API_TOKEN\"]\nTRANSACTIONAL_INVOICE_TEMPLATE = os.environ[\"TRANSACTIONAL_INVOICE_TEMPLATE_UUID\"]\n\n# A view that streams the PDF inline as the response\nfrom django.http import HttpResponse\nfrom django.conf import settings\nimport httpx\n\ndef download_invoice(request, invoice_id):\n    invoice = get_object_or_404(Invoice, id=invoice_id, user=request.user)\n\n    res = httpx.post(\n        \"https:\u002F\u002Fapi.transactional.dev\u002Fv1\u002Fgenerate\",\n        json={\n            \"documentId\": settings.TRANSACTIONAL_INVOICE_TEMPLATE,\n            \"variables\": invoice.template_variables(),\n        },\n        headers={\"x-api-token\": settings.TRANSACTIONAL_API_TOKEN},\n        timeout=30,\n    )\n    res.raise_for_status()\n\n    pdf = httpx.get(res.json()[\"url\"], timeout=30).content\n    response = HttpResponse(pdf, content_type=\"application\u002Fpdf\")\n    response[\"Content-Disposition\"] = f'attachment; filename=\"invoice-{invoice.number}.pdf\"'\n    return response\n",[19,512,513,518,523,528,532,537,542,547,551,555,560,565,569,574,578,583,588,593,597,602,607,611,616,620,625,630,635],{"__ignoreMap":33},[37,514,515],{"class":39,"line":40},[37,516,517],{},"# settings.py\n",[37,519,520],{"class":39,"line":46},[37,521,522],{},"TRANSACTIONAL_API_TOKEN = os.environ[\"TRANSACTIONAL_API_TOKEN\"]\n",[37,524,525],{"class":39,"line":52},[37,526,527],{},"TRANSACTIONAL_INVOICE_TEMPLATE = os.environ[\"TRANSACTIONAL_INVOICE_TEMPLATE_UUID\"]\n",[37,529,530],{"class":39,"line":58},[37,531,68],{"emptyLinePlaceholder":67},[37,533,534],{"class":39,"line":64},[37,535,536],{},"# A view that streams the PDF inline as the response\n",[37,538,539],{"class":39,"line":71},[37,540,541],{},"from django.http import HttpResponse\n",[37,543,544],{"class":39,"line":77},[37,545,546],{},"from django.conf import settings\n",[37,548,549],{"class":39,"line":83},[37,550,286],{},[37,552,553],{"class":39,"line":89},[37,554,68],{"emptyLinePlaceholder":67},[37,556,557],{"class":39,"line":95},[37,558,559],{},"def download_invoice(request, invoice_id):\n",[37,561,562],{"class":39,"line":101},[37,563,564],{},"    invoice = get_object_or_404(Invoice, id=invoice_id, user=request.user)\n",[37,566,567],{"class":39,"line":106},[37,568,68],{"emptyLinePlaceholder":67},[37,570,571],{"class":39,"line":112},[37,572,573],{},"    res = httpx.post(\n",[37,575,576],{"class":39,"line":118},[37,577,115],{},[37,579,580],{"class":39,"line":124},[37,581,582],{},"        json={\n",[37,584,585],{"class":39,"line":130},[37,586,587],{},"            \"documentId\": settings.TRANSACTIONAL_INVOICE_TEMPLATE,\n",[37,589,590],{"class":39,"line":136},[37,591,592],{},"            \"variables\": invoice.template_variables(),\n",[37,594,595],{"class":39,"line":142},[37,596,145],{},[37,598,599],{"class":39,"line":148},[37,600,601],{},"        headers={\"x-api-token\": settings.TRANSACTIONAL_API_TOKEN},\n",[37,603,604],{"class":39,"line":154},[37,605,606],{},"        timeout=30,\n",[37,608,609],{"class":39,"line":160},[37,610,157],{},[37,612,613],{"class":39,"line":165},[37,614,615],{},"    res.raise_for_status()\n",[37,617,618],{"class":39,"line":171},[37,619,68],{"emptyLinePlaceholder":67},[37,621,622],{"class":39,"line":177},[37,623,624],{},"    pdf = httpx.get(res.json()[\"url\"], timeout=30).content\n",[37,626,627],{"class":39,"line":183},[37,628,629],{},"    response = HttpResponse(pdf, content_type=\"application\u002Fpdf\")\n",[37,631,632],{"class":39,"line":189},[37,633,634],{},"    response[\"Content-Disposition\"] = f'attachment; filename=\"invoice-{invoice.number}.pdf\"'\n",[37,636,637],{"class":39,"line":195},[37,638,639],{},"    return response\n",[15,641,642],{},"For background generation, push to Celery:",[29,644,646],{"className":31,"code":645,"language":12,"meta":33,"style":33},"@shared_task(bind=True, autoretry_for=(httpx.HTTPError,), max_retries=3, retry_backoff=True)\ndef generate_invoice_pdf(self, invoice_id):\n    invoice = Invoice.objects.get(id=invoice_id)\n    # ... same as above, then save to S3 \u002F Django storages\n",[19,647,648,653,658,663],{"__ignoreMap":33},[37,649,650],{"class":39,"line":40},[37,651,652],{},"@shared_task(bind=True, autoretry_for=(httpx.HTTPError,), max_retries=3, retry_backoff=True)\n",[37,654,655],{"class":39,"line":46},[37,656,657],{},"def generate_invoice_pdf(self, invoice_id):\n",[37,659,660],{"class":39,"line":52},[37,661,662],{},"    invoice = Invoice.objects.get(id=invoice_id)\n",[37,664,665],{"class":39,"line":58},[37,666,667],{},"    # ... same as above, then save to S3 \u002F Django storages\n",[24,669,671],{"id":670},"fastapi-integration","FastAPI integration",[29,673,675],{"className":31,"code":674,"language":12,"meta":33,"style":33},"from fastapi import FastAPI, HTTPException\nimport httpx\nimport os\n\napp = FastAPI()\n\n@app.post(\"\u002Finvoices\u002F{id}\u002Fpdf\")\nasync def render_invoice(id: int):\n    invoice = await Invoice.get(id)\n    async with httpx.AsyncClient(timeout=30) as client:\n        res = await client.post(\n            \"https:\u002F\u002Fapi.transactional.dev\u002Fv1\u002Fgenerate\",\n            json={\n                \"documentId\": os.environ[\"TRANSACTIONAL_INVOICE_TEMPLATE_UUID\"],\n                \"variables\": invoice.to_template_vars(),\n            },\n            headers={\"x-api-token\": os.environ[\"TRANSACTIONAL_API_TOKEN\"]},\n        )\n    if res.is_error:\n        body = res.json()\n        raise HTTPException(res.status_code, detail=body)\n    return res.json()  # {url, documentId}\n",[19,676,677,682,686,690,694,699,703,708,713,718,722,727,732,737,742,747,752,756,760,765,770,775],{"__ignoreMap":33},[37,678,679],{"class":39,"line":40},[37,680,681],{},"from fastapi import FastAPI, HTTPException\n",[37,683,684],{"class":39,"line":46},[37,685,286],{},[37,687,688],{"class":39,"line":52},[37,689,49],{},[37,691,692],{"class":39,"line":58},[37,693,68],{"emptyLinePlaceholder":67},[37,695,696],{"class":39,"line":64},[37,697,698],{},"app = FastAPI()\n",[37,700,701],{"class":39,"line":71},[37,702,68],{"emptyLinePlaceholder":67},[37,704,705],{"class":39,"line":77},[37,706,707],{},"@app.post(\"\u002Finvoices\u002F{id}\u002Fpdf\")\n",[37,709,710],{"class":39,"line":83},[37,711,712],{},"async def render_invoice(id: int):\n",[37,714,715],{"class":39,"line":89},[37,716,717],{},"    invoice = await Invoice.get(id)\n",[37,719,720],{"class":39,"line":95},[37,721,413],{},[37,723,724],{"class":39,"line":101},[37,725,726],{},"        res = await client.post(\n",[37,728,729],{"class":39,"line":106},[37,730,731],{},"            \"https:\u002F\u002Fapi.transactional.dev\u002Fv1\u002Fgenerate\",\n",[37,733,734],{"class":39,"line":112},[37,735,736],{},"            json={\n",[37,738,739],{"class":39,"line":118},[37,740,741],{},"                \"documentId\": os.environ[\"TRANSACTIONAL_INVOICE_TEMPLATE_UUID\"],\n",[37,743,744],{"class":39,"line":124},[37,745,746],{},"                \"variables\": invoice.to_template_vars(),\n",[37,748,749],{"class":39,"line":130},[37,750,751],{},"            },\n",[37,753,754],{"class":39,"line":136},[37,755,337],{},[37,757,758],{"class":39,"line":142},[37,759,342],{},[37,761,762],{"class":39,"line":148},[37,763,764],{},"    if res.is_error:\n",[37,766,767],{"class":39,"line":154},[37,768,769],{},"        body = res.json()\n",[37,771,772],{"class":39,"line":160},[37,773,774],{},"        raise HTTPException(res.status_code, detail=body)\n",[37,776,777],{"class":39,"line":165},[37,778,779],{},"    return res.json()  # {url, documentId}\n",[781,782,795],"aside",{"className":783},[784,785,786,787,788,789,790,791,792,793,794],"not-prose","my-6","rounded-md","border","border-blue-200","bg-blue-50","text-blue-900","px-4","py-3","text-[14px]","leading-relaxed",[15,796,797,805],{},[798,799,800,801,804],"strong",{},"The ",[19,802,803],{},"url"," returned is short-lived."," Either fetch the PDF immediately or hand the URL to the user with an \"expires soon\" hint.",[24,807,809],{"id":808},"next-steps","Next steps",[811,812,813,823],"ul",{},[814,815,816],"li",{},[798,817,818],{},[819,820,822],"a",{"href":821},"\u002Fdocs\u002Fguides\u002Foperations\u002Fstoring-pdfs","Storing & serving the generated PDF →",[814,824,825],{},[798,826,827],{},[819,828,830],{"href":829},"\u002Fdocs\u002Fguides\u002Foperations\u002Fmonitoring-usage","Monitoring usage & credits →",[832,833,834],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":33,"searchDepth":46,"depth":46,"links":836},[837,838,839,840,841,842],{"id":26,"depth":46,"text":27},{"id":271,"depth":46,"text":272},{"id":447,"depth":46,"text":448},{"id":506,"depth":46,"text":507},{"id":670,"depth":46,"text":671},{"id":808,"depth":46,"text":809},"integrations","urllib (stdlib), requests, or httpx with retry — render PDFs from Django, FastAPI, or any Python service.","md","code-2",{},"\u002Fdocs\u002Fguides\u002Fintegrations\u002Fpython",{"title":5,"description":844},"docs\u002Fguides\u002Fintegrations\u002Fpython","JVz11CFRNPqDDulxlUMnOF7CzMpk2x-22te1a54Bzi8",[853,858,862,866,870,874,878,882,887,891,895,899,904,908,912,913,917],{"path":854,"title":855,"description":856,"category":857,"order":64},"\u002Fdocs\u002Fguides\u002Fai-mcp\u002Fchatgpt","ChatGPT (via Custom GPT Actions)","ChatGPT doesn't speak MCP natively yet — but you can give it the same powers through a Custom GPT pointed at the Transactional REST API.","ai-mcp",{"path":859,"title":860,"description":861,"category":857,"order":52},"\u002Fdocs\u002Fguides\u002Fai-mcp\u002Fclaude-code","Claude Code (CLI)","Connect Transactional to Claude Code so it can read your templates and generate PDFs from your terminal.",{"path":863,"title":864,"description":865,"category":857,"order":46},"\u002Fdocs\u002Fguides\u002Fai-mcp\u002Fclaude-desktop","Claude Desktop","Connect Transactional to Claude Desktop on macOS or Windows so Claude can read your templates and generate PDFs.",{"path":867,"title":868,"description":869,"category":857,"order":58},"\u002Fdocs\u002Fguides\u002Fai-mcp\u002Fcursor","Cursor","Wire Transactional into Cursor's MCP support so you can generate PDFs from inside your editor.",{"path":871,"title":872,"description":873,"category":857,"order":71},"\u002Fdocs\u002Fguides\u002Fai-mcp\u002Fgemini","Gemini Code Assist \u002F Gemini CLI","Connect Transactional to Google's Gemini agents through their MCP support.",{"path":875,"title":876,"description":877,"category":857,"order":77},"\u002Fdocs\u002Fguides\u002Fai-mcp\u002Ftools-reference","MCP tools reference","Every tool the Transactional MCP server exposes, with arguments, return shapes, and a prompt that typically triggers each one.",{"path":879,"title":880,"description":881,"category":857,"order":40},"\u002Fdocs\u002Fguides\u002Fai-mcp\u002Fuse-from-ai","Use Transactional from your AI assistant","Connect Transactional to Claude, Cursor, ChatGPT, or Gemini via MCP so your assistant can read your templates and generate PDFs directly.",{"path":883,"title":884,"description":885,"category":886,"order":46},"\u002Fdocs\u002Fguides\u002Fauthoring\u002Fdesign-for-pdf","Designing templates that survive PDF rendering","PDFs are static — drop the animations, oversample your canvas charts, lean on vectors. The rules that make a template look sharp at print resolution.","authoring",{"path":888,"title":889,"description":890,"category":886,"order":40},"\u002Fdocs\u002Fguides\u002Fauthoring\u002Fhandlebars","Handlebars cheat sheet","The exact subset of Handlebars supported in Transactional templates — variables, conditionals, loops, and what NOT to reach for.",{"path":892,"title":893,"description":894,"category":886,"order":52},"\u002Fdocs\u002Fguides\u002Fauthoring\u002Fmodeling-variables","Modeling your variables","When to make something a variable vs. inline. Keep the API contract small, your templates portable, and your integration code boring.",{"path":896,"title":897,"description":898,"category":886,"order":58},"\u002Fdocs\u002Fguides\u002Fauthoring\u002Fworking-with-ai","Working with the AI assistant","Prompts and patterns to get good templates fast — what to ask, when to iterate, when to start over.",{"path":900,"title":901,"description":902,"category":903,"order":40},"\u002Fdocs\u002Fguides\u002Fgetting-started\u002Fquickstart","Quickstart — your first PDF in 5 minutes","Sign up, design a template, render your first PDF through the API. End-to-end in five minutes.","getting-started",{"path":905,"title":906,"description":907,"category":843,"order":40},"\u002Fdocs\u002Fguides\u002Fintegrations\u002Fnode-bun","Calling \u002Fv1\u002Fgenerate from Node.js & Bun","Production-grade integration using native fetch — retries, error handling, streaming the PDF to your storage.",{"path":909,"title":910,"description":911,"category":843,"order":46},"\u002Fdocs\u002Fguides\u002Fintegrations\u002Fphp-laravel","Calling \u002Fv1\u002Fgenerate from PHP & Laravel","cURL extension, Guzzle, or Laravel's HTTP client — render PDFs with retries and proper error handling.",{"path":848,"title":5,"description":844,"category":843,"order":52},{"path":829,"title":914,"description":915,"category":916,"order":40},"Monitoring usage & credits","Read the dashboard gauges, set sane alerts, and decide when to top up vs. upgrade.","operations",{"path":821,"title":918,"description":919,"category":916,"order":46},"Storing & serving the generated PDF","The \u002Fv1\u002Fgenerate URL is signed and short-lived. Patterns for keeping the PDF around — your bucket, your CDN, your DB.",1780347733851]