[{"data":1,"prerenderedAt":913},["ShallowReactive",2],{"guide-\u002Fdocs\u002Fguides\u002Fintegrations\u002Fphp-laravel":3,"guides-all":845},{"id":4,"title":5,"body":6,"category":837,"description":838,"extension":839,"icon":31,"meta":840,"navigation":46,"order":43,"path":841,"seo":842,"stem":843,"__hash__":844},"guides\u002Fdocs\u002Fguides\u002Fintegrations\u002Fphp-laravel.md","Calling \u002Fv1\u002Fgenerate from PHP & Laravel",{"type":7,"value":8,"toc":829},"minimark",[9,14,18,23,278,282,285,395,406,431,435,489,493,496,681,711,715,800,804,825],[10,11,13],"h1",{"id":12},"php-laravel","PHP & Laravel",[15,16,17],"p",{},"The Transactional client API is a single POST. The built-in cURL extension is enough — no Composer package required. If you're on Laravel, the framework's HTTP client is even nicer.",[19,20,22],"h2",{"id":21},"plain-php-curl-extension","Plain PHP (cURL extension)",[24,25,30],"pre",{"className":26,"code":27,"language":28,"meta":29,"style":29},"language-php shiki shiki-themes github-light github-dark","\u003C?php\n\nfunction transactionalGenerate(string $documentId, array $variables): array\n{\n    $ch = curl_init('https:\u002F\u002Fapi.transactional.dev\u002Fv1\u002Fgenerate');\n    curl_setopt_array($ch, [\n        CURLOPT_POST => true,\n        CURLOPT_RETURNTRANSFER => true,\n        CURLOPT_HTTPHEADER => [\n            'x-api-token: ' . getenv('TRANSACTIONAL_API_TOKEN'),\n            'Content-Type: application\u002Fjson',\n        ],\n        CURLOPT_POSTFIELDS => json_encode([\n            'documentId' => $documentId,\n            'variables'  => $variables,\n        ]),\n        CURLOPT_TIMEOUT => 30,\n    ]);\n\n    $body = curl_exec($ch);\n    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n    curl_close($ch);\n\n    $data = json_decode($body, true);\n    if ($status \u003C 200 || $status >= 300) {\n        throw new RuntimeException(\n            \"Transactional {$status}: \" . ($data['error'] ?? 'unknown') . ' — ' . ($data['message'] ?? '')\n        );\n    }\n    return $data; \u002F\u002F ['url' => '...', 'documentId' => '...']\n}\n\n$result = transactionalGenerate(\n    '1d8e5d56-1a4f-4b62-8c33-2d34a64b2f00',\n    [\n        'customer' => ['name' => 'Acme Corp'],\n        'invoice'  => ['number' => 'INV-2026-0142', 'total' => 1280.50],\n    ]\n);\n\necho $result['url'];\n","php","",[31,32,33,41,48,54,60,66,72,78,84,90,96,102,108,114,120,126,132,138,144,149,155,161,167,172,178,184,190,196,202,208,214,220,225,231,237,243,249,255,261,267,272],"code",{"__ignoreMap":29},[34,35,38],"span",{"class":36,"line":37},"line",1,[34,39,40],{},"\u003C?php\n",[34,42,44],{"class":36,"line":43},2,[34,45,47],{"emptyLinePlaceholder":46},true,"\n",[34,49,51],{"class":36,"line":50},3,[34,52,53],{},"function transactionalGenerate(string $documentId, array $variables): array\n",[34,55,57],{"class":36,"line":56},4,[34,58,59],{},"{\n",[34,61,63],{"class":36,"line":62},5,[34,64,65],{},"    $ch = curl_init('https:\u002F\u002Fapi.transactional.dev\u002Fv1\u002Fgenerate');\n",[34,67,69],{"class":36,"line":68},6,[34,70,71],{},"    curl_setopt_array($ch, [\n",[34,73,75],{"class":36,"line":74},7,[34,76,77],{},"        CURLOPT_POST => true,\n",[34,79,81],{"class":36,"line":80},8,[34,82,83],{},"        CURLOPT_RETURNTRANSFER => true,\n",[34,85,87],{"class":36,"line":86},9,[34,88,89],{},"        CURLOPT_HTTPHEADER => [\n",[34,91,93],{"class":36,"line":92},10,[34,94,95],{},"            'x-api-token: ' . getenv('TRANSACTIONAL_API_TOKEN'),\n",[34,97,99],{"class":36,"line":98},11,[34,100,101],{},"            'Content-Type: application\u002Fjson',\n",[34,103,105],{"class":36,"line":104},12,[34,106,107],{},"        ],\n",[34,109,111],{"class":36,"line":110},13,[34,112,113],{},"        CURLOPT_POSTFIELDS => json_encode([\n",[34,115,117],{"class":36,"line":116},14,[34,118,119],{},"            'documentId' => $documentId,\n",[34,121,123],{"class":36,"line":122},15,[34,124,125],{},"            'variables'  => $variables,\n",[34,127,129],{"class":36,"line":128},16,[34,130,131],{},"        ]),\n",[34,133,135],{"class":36,"line":134},17,[34,136,137],{},"        CURLOPT_TIMEOUT => 30,\n",[34,139,141],{"class":36,"line":140},18,[34,142,143],{},"    ]);\n",[34,145,147],{"class":36,"line":146},19,[34,148,47],{"emptyLinePlaceholder":46},[34,150,152],{"class":36,"line":151},20,[34,153,154],{},"    $body = curl_exec($ch);\n",[34,156,158],{"class":36,"line":157},21,[34,159,160],{},"    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n",[34,162,164],{"class":36,"line":163},22,[34,165,166],{},"    curl_close($ch);\n",[34,168,170],{"class":36,"line":169},23,[34,171,47],{"emptyLinePlaceholder":46},[34,173,175],{"class":36,"line":174},24,[34,176,177],{},"    $data = json_decode($body, true);\n",[34,179,181],{"class":36,"line":180},25,[34,182,183],{},"    if ($status \u003C 200 || $status >= 300) {\n",[34,185,187],{"class":36,"line":186},26,[34,188,189],{},"        throw new RuntimeException(\n",[34,191,193],{"class":36,"line":192},27,[34,194,195],{},"            \"Transactional {$status}: \" . ($data['error'] ?? 'unknown') . ' — ' . ($data['message'] ?? '')\n",[34,197,199],{"class":36,"line":198},28,[34,200,201],{},"        );\n",[34,203,205],{"class":36,"line":204},29,[34,206,207],{},"    }\n",[34,209,211],{"class":36,"line":210},30,[34,212,213],{},"    return $data; \u002F\u002F ['url' => '...', 'documentId' => '...']\n",[34,215,217],{"class":36,"line":216},31,[34,218,219],{},"}\n",[34,221,223],{"class":36,"line":222},32,[34,224,47],{"emptyLinePlaceholder":46},[34,226,228],{"class":36,"line":227},33,[34,229,230],{},"$result = transactionalGenerate(\n",[34,232,234],{"class":36,"line":233},34,[34,235,236],{},"    '1d8e5d56-1a4f-4b62-8c33-2d34a64b2f00',\n",[34,238,240],{"class":36,"line":239},35,[34,241,242],{},"    [\n",[34,244,246],{"class":36,"line":245},36,[34,247,248],{},"        'customer' => ['name' => 'Acme Corp'],\n",[34,250,252],{"class":36,"line":251},37,[34,253,254],{},"        'invoice'  => ['number' => 'INV-2026-0142', 'total' => 1280.50],\n",[34,256,258],{"class":36,"line":257},38,[34,259,260],{},"    ]\n",[34,262,264],{"class":36,"line":263},39,[34,265,266],{},");\n",[34,268,270],{"class":36,"line":269},40,[34,271,47],{"emptyLinePlaceholder":46},[34,273,275],{"class":36,"line":274},41,[34,276,277],{},"echo $result['url'];\n",[19,279,281],{"id":280},"laravel-http-client","Laravel HTTP client",[15,283,284],{},"Laravel's HTTP client (built on Guzzle) gives you retries and timeouts in one line.",[24,286,288],{"className":26,"code":287,"language":28,"meta":29,"style":29},"use Illuminate\\Support\\Facades\\Http;\n\n$response = Http::withHeaders([\n    'x-api-token' => config('services.transactional.token'),\n])\n    ->timeout(30)\n    ->retry(2, 200, throw: false) \u002F\u002F 2 retries, 200ms baseline\n    ->post('https:\u002F\u002Fapi.transactional.dev\u002Fv1\u002Fgenerate', [\n        'documentId' => $documentId,\n        'variables'  => $variables,\n    ]);\n\nif ($response->failed()) {\n    $body = $response->json();\n    throw new \\RuntimeException(\n        \"Transactional {$response->status()}: \"\n        . ($body['error'] ?? 'unknown') . ' — '\n        . ($body['message'] ?? '')\n    );\n}\n\nreturn $response->json('url');\n",[31,289,290,295,299,304,309,314,319,324,329,334,339,343,347,352,357,362,367,372,377,382,386,390],{"__ignoreMap":29},[34,291,292],{"class":36,"line":37},[34,293,294],{},"use Illuminate\\Support\\Facades\\Http;\n",[34,296,297],{"class":36,"line":43},[34,298,47],{"emptyLinePlaceholder":46},[34,300,301],{"class":36,"line":50},[34,302,303],{},"$response = Http::withHeaders([\n",[34,305,306],{"class":36,"line":56},[34,307,308],{},"    'x-api-token' => config('services.transactional.token'),\n",[34,310,311],{"class":36,"line":62},[34,312,313],{},"])\n",[34,315,316],{"class":36,"line":68},[34,317,318],{},"    ->timeout(30)\n",[34,320,321],{"class":36,"line":74},[34,322,323],{},"    ->retry(2, 200, throw: false) \u002F\u002F 2 retries, 200ms baseline\n",[34,325,326],{"class":36,"line":80},[34,327,328],{},"    ->post('https:\u002F\u002Fapi.transactional.dev\u002Fv1\u002Fgenerate', [\n",[34,330,331],{"class":36,"line":86},[34,332,333],{},"        'documentId' => $documentId,\n",[34,335,336],{"class":36,"line":92},[34,337,338],{},"        'variables'  => $variables,\n",[34,340,341],{"class":36,"line":98},[34,342,143],{},[34,344,345],{"class":36,"line":104},[34,346,47],{"emptyLinePlaceholder":46},[34,348,349],{"class":36,"line":110},[34,350,351],{},"if ($response->failed()) {\n",[34,353,354],{"class":36,"line":116},[34,355,356],{},"    $body = $response->json();\n",[34,358,359],{"class":36,"line":122},[34,360,361],{},"    throw new \\RuntimeException(\n",[34,363,364],{"class":36,"line":128},[34,365,366],{},"        \"Transactional {$response->status()}: \"\n",[34,368,369],{"class":36,"line":134},[34,370,371],{},"        . ($body['error'] ?? 'unknown') . ' — '\n",[34,373,374],{"class":36,"line":140},[34,375,376],{},"        . ($body['message'] ?? '')\n",[34,378,379],{"class":36,"line":146},[34,380,381],{},"    );\n",[34,383,384],{"class":36,"line":151},[34,385,219],{},[34,387,388],{"class":36,"line":157},[34,389,47],{"emptyLinePlaceholder":46},[34,391,392],{"class":36,"line":163},[34,393,394],{},"return $response->json('url');\n",[15,396,397,398,401,402,405],{},"Add the token to ",[31,399,400],{},"config\u002Fservices.php"," and pull it from ",[31,403,404],{},".env",":",[24,407,409],{"className":26,"code":408,"language":28,"meta":29,"style":29},"\u002F\u002F config\u002Fservices.php\n'transactional' => [\n    'token' => env('TRANSACTIONAL_API_TOKEN'),\n],\n",[31,410,411,416,421,426],{"__ignoreMap":29},[34,412,413],{"class":36,"line":37},[34,414,415],{},"\u002F\u002F config\u002Fservices.php\n",[34,417,418],{"class":36,"line":43},[34,419,420],{},"'transactional' => [\n",[34,422,423],{"class":36,"line":50},[34,424,425],{},"    'token' => env('TRANSACTIONAL_API_TOKEN'),\n",[34,427,428],{"class":36,"line":56},[34,429,430],{},"],\n",[19,432,434],{"id":433},"branching-on-error-codes","Branching on error codes",[24,436,438],{"className":26,"code":437,"language":28,"meta":29,"style":29},"$body = $response->json();\n$code = $body['error'] ?? null;\n\nmatch (true) {\n    $response->successful()                          => $body['url'],\n    $code === 'quota_exceeded'                        => throw new OutOfCreditsException(),\n    in_array($code, ['NOT_FOUND', 'invalid_document_id']) => throw new BadTemplateException($documentId),\n    $code === 'UNAUTHORIZED'                          => throw new ApiTokenInvalidException(),\n    default                                           => throw new RuntimeException('Transactional unknown error'),\n};\n",[31,439,440,445,450,454,459,464,469,474,479,484],{"__ignoreMap":29},[34,441,442],{"class":36,"line":37},[34,443,444],{},"$body = $response->json();\n",[34,446,447],{"class":36,"line":43},[34,448,449],{},"$code = $body['error'] ?? null;\n",[34,451,452],{"class":36,"line":50},[34,453,47],{"emptyLinePlaceholder":46},[34,455,456],{"class":36,"line":56},[34,457,458],{},"match (true) {\n",[34,460,461],{"class":36,"line":62},[34,462,463],{},"    $response->successful()                          => $body['url'],\n",[34,465,466],{"class":36,"line":68},[34,467,468],{},"    $code === 'quota_exceeded'                        => throw new OutOfCreditsException(),\n",[34,470,471],{"class":36,"line":74},[34,472,473],{},"    in_array($code, ['NOT_FOUND', 'invalid_document_id']) => throw new BadTemplateException($documentId),\n",[34,475,476],{"class":36,"line":80},[34,477,478],{},"    $code === 'UNAUTHORIZED'                          => throw new ApiTokenInvalidException(),\n",[34,480,481],{"class":36,"line":86},[34,482,483],{},"    default                                           => throw new RuntimeException('Transactional unknown error'),\n",[34,485,486],{"class":36,"line":92},[34,487,488],{},"};\n",[19,490,492],{"id":491},"queueing-as-a-job-laravel","Queueing as a job (Laravel)",[15,494,495],{},"Generating from a synchronous request blocks the HTTP cycle for a few hundred ms. Push it to a queue:",[24,497,499],{"className":26,"code":498,"language":28,"meta":29,"style":29},"\u002F\u002F app\u002FJobs\u002FGenerateInvoicePdf.php\nclass GenerateInvoicePdf implements ShouldQueue\n{\n    use Queueable;\n\n    public function __construct(\n        public int $invoiceId,\n        public string $documentUuid,\n    ) {}\n\n    public function handle(): void\n    {\n        $invoice = Invoice::findOrFail($this->invoiceId);\n\n        $response = Http::withHeaders([\n            'x-api-token' => config('services.transactional.token'),\n        ])->retry(2, 200, throw: false)\n          ->post('https:\u002F\u002Fapi.transactional.dev\u002Fv1\u002Fgenerate', [\n              'documentId' => $this->documentUuid,\n              'variables'  => $invoice->toTemplateVariables(),\n          ]);\n\n        if ($response->failed()) {\n            \u002F\u002F Let the queue retry policy handle it.\n            $this->fail(\"Transactional failed: \" . $response->status());\n            return;\n        }\n\n        \u002F\u002F Stream the PDF to S3.\n        $pdf = Http::get($response->json('url'))->body();\n        Storage::disk('s3')->put(\"invoices\u002F{$invoice->id}.pdf\", $pdf);\n\n        $invoice->update(['pdf_path' => \"invoices\u002F{$invoice->id}.pdf\"]);\n    }\n}\n\n\u002F\u002F Dispatch it\nGenerateInvoicePdf::dispatch($invoice->id, $templateUuid);\n",[31,500,501,506,511,515,520,524,529,534,539,544,548,553,558,563,567,572,577,582,587,592,597,602,606,611,616,621,626,631,635,640,645,650,654,659,663,667,671,676],{"__ignoreMap":29},[34,502,503],{"class":36,"line":37},[34,504,505],{},"\u002F\u002F app\u002FJobs\u002FGenerateInvoicePdf.php\n",[34,507,508],{"class":36,"line":43},[34,509,510],{},"class GenerateInvoicePdf implements ShouldQueue\n",[34,512,513],{"class":36,"line":50},[34,514,59],{},[34,516,517],{"class":36,"line":56},[34,518,519],{},"    use Queueable;\n",[34,521,522],{"class":36,"line":62},[34,523,47],{"emptyLinePlaceholder":46},[34,525,526],{"class":36,"line":68},[34,527,528],{},"    public function __construct(\n",[34,530,531],{"class":36,"line":74},[34,532,533],{},"        public int $invoiceId,\n",[34,535,536],{"class":36,"line":80},[34,537,538],{},"        public string $documentUuid,\n",[34,540,541],{"class":36,"line":86},[34,542,543],{},"    ) {}\n",[34,545,546],{"class":36,"line":92},[34,547,47],{"emptyLinePlaceholder":46},[34,549,550],{"class":36,"line":98},[34,551,552],{},"    public function handle(): void\n",[34,554,555],{"class":36,"line":104},[34,556,557],{},"    {\n",[34,559,560],{"class":36,"line":110},[34,561,562],{},"        $invoice = Invoice::findOrFail($this->invoiceId);\n",[34,564,565],{"class":36,"line":116},[34,566,47],{"emptyLinePlaceholder":46},[34,568,569],{"class":36,"line":122},[34,570,571],{},"        $response = Http::withHeaders([\n",[34,573,574],{"class":36,"line":128},[34,575,576],{},"            'x-api-token' => config('services.transactional.token'),\n",[34,578,579],{"class":36,"line":134},[34,580,581],{},"        ])->retry(2, 200, throw: false)\n",[34,583,584],{"class":36,"line":140},[34,585,586],{},"          ->post('https:\u002F\u002Fapi.transactional.dev\u002Fv1\u002Fgenerate', [\n",[34,588,589],{"class":36,"line":146},[34,590,591],{},"              'documentId' => $this->documentUuid,\n",[34,593,594],{"class":36,"line":151},[34,595,596],{},"              'variables'  => $invoice->toTemplateVariables(),\n",[34,598,599],{"class":36,"line":157},[34,600,601],{},"          ]);\n",[34,603,604],{"class":36,"line":163},[34,605,47],{"emptyLinePlaceholder":46},[34,607,608],{"class":36,"line":169},[34,609,610],{},"        if ($response->failed()) {\n",[34,612,613],{"class":36,"line":174},[34,614,615],{},"            \u002F\u002F Let the queue retry policy handle it.\n",[34,617,618],{"class":36,"line":180},[34,619,620],{},"            $this->fail(\"Transactional failed: \" . $response->status());\n",[34,622,623],{"class":36,"line":186},[34,624,625],{},"            return;\n",[34,627,628],{"class":36,"line":192},[34,629,630],{},"        }\n",[34,632,633],{"class":36,"line":198},[34,634,47],{"emptyLinePlaceholder":46},[34,636,637],{"class":36,"line":204},[34,638,639],{},"        \u002F\u002F Stream the PDF to S3.\n",[34,641,642],{"class":36,"line":210},[34,643,644],{},"        $pdf = Http::get($response->json('url'))->body();\n",[34,646,647],{"class":36,"line":216},[34,648,649],{},"        Storage::disk('s3')->put(\"invoices\u002F{$invoice->id}.pdf\", $pdf);\n",[34,651,652],{"class":36,"line":222},[34,653,47],{"emptyLinePlaceholder":46},[34,655,656],{"class":36,"line":227},[34,657,658],{},"        $invoice->update(['pdf_path' => \"invoices\u002F{$invoice->id}.pdf\"]);\n",[34,660,661],{"class":36,"line":233},[34,662,207],{},[34,664,665],{"class":36,"line":239},[34,666,219],{},[34,668,669],{"class":36,"line":245},[34,670,47],{"emptyLinePlaceholder":46},[34,672,673],{"class":36,"line":251},[34,674,675],{},"\u002F\u002F Dispatch it\n",[34,677,678],{"class":36,"line":257},[34,679,680],{},"GenerateInvoicePdf::dispatch($invoice->id, $templateUuid);\n",[682,683,696],"aside",{"className":684},[685,686,687,688,689,690,691,692,693,694,695],"not-prose","my-6","rounded-md","border","border-amber-200","bg-amber-50","text-amber-900","px-4","py-3","text-[14px]","leading-relaxed",[15,697,698,706,707,710],{},[699,700,701,702,705],"strong",{},"Don't store the signed ",[31,703,704],{},"url"," in your DB."," It expires. Either fetch and stash the bytes yourself (as above), or recall ",[31,708,709],{},"\u002Fv1\u002Fgenerate"," next time you need the PDF.",[19,712,714],{"id":713},"attaching-the-pdf-to-a-mailable","Attaching the PDF to a Mailable",[24,716,718],{"className":26,"code":717,"language":28,"meta":29,"style":29},"\u002F\u002F app\u002FMail\u002FInvoiceMail.php\npublic function attachments(): array\n{\n    $response = Http::withHeaders([\n        'x-api-token' => config('services.transactional.token'),\n    ])->post('https:\u002F\u002Fapi.transactional.dev\u002Fv1\u002Fgenerate', [\n        'documentId' => config('invoices.template_uuid'),\n        'variables'  => $this->invoice->toTemplateVariables(),\n    ]);\n\n    $pdf = Http::get($response->json('url'))->body();\n\n    return [\n        Attachment::fromData(fn () => $pdf, \"invoice-{$this->invoice->number}.pdf\")\n            ->withMime('application\u002Fpdf'),\n    ];\n}\n",[31,719,720,725,730,734,739,744,749,754,759,763,767,772,776,781,786,791,796],{"__ignoreMap":29},[34,721,722],{"class":36,"line":37},[34,723,724],{},"\u002F\u002F app\u002FMail\u002FInvoiceMail.php\n",[34,726,727],{"class":36,"line":43},[34,728,729],{},"public function attachments(): array\n",[34,731,732],{"class":36,"line":50},[34,733,59],{},[34,735,736],{"class":36,"line":56},[34,737,738],{},"    $response = Http::withHeaders([\n",[34,740,741],{"class":36,"line":62},[34,742,743],{},"        'x-api-token' => config('services.transactional.token'),\n",[34,745,746],{"class":36,"line":68},[34,747,748],{},"    ])->post('https:\u002F\u002Fapi.transactional.dev\u002Fv1\u002Fgenerate', [\n",[34,750,751],{"class":36,"line":74},[34,752,753],{},"        'documentId' => config('invoices.template_uuid'),\n",[34,755,756],{"class":36,"line":80},[34,757,758],{},"        'variables'  => $this->invoice->toTemplateVariables(),\n",[34,760,761],{"class":36,"line":86},[34,762,143],{},[34,764,765],{"class":36,"line":92},[34,766,47],{"emptyLinePlaceholder":46},[34,768,769],{"class":36,"line":98},[34,770,771],{},"    $pdf = Http::get($response->json('url'))->body();\n",[34,773,774],{"class":36,"line":104},[34,775,47],{"emptyLinePlaceholder":46},[34,777,778],{"class":36,"line":110},[34,779,780],{},"    return [\n",[34,782,783],{"class":36,"line":116},[34,784,785],{},"        Attachment::fromData(fn () => $pdf, \"invoice-{$this->invoice->number}.pdf\")\n",[34,787,788],{"class":36,"line":122},[34,789,790],{},"            ->withMime('application\u002Fpdf'),\n",[34,792,793],{"class":36,"line":128},[34,794,795],{},"    ];\n",[34,797,798],{"class":36,"line":134},[34,799,219],{},[19,801,803],{"id":802},"next-steps","Next steps",[805,806,807,817],"ul",{},[808,809,810],"li",{},[699,811,812],{},[813,814,816],"a",{"href":815},"\u002Fdocs\u002Fguides\u002Foperations\u002Fstoring-pdfs","Storing & serving the generated PDF →",[808,818,819],{},[699,820,821],{},[813,822,824],{"href":823},"\u002Fdocs\u002Fguides\u002Foperations\u002Fmonitoring-usage","Monitoring usage & credits →",[826,827,828],"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":29,"searchDepth":43,"depth":43,"links":830},[831,832,833,834,835,836],{"id":21,"depth":43,"text":22},{"id":280,"depth":43,"text":281},{"id":433,"depth":43,"text":434},{"id":491,"depth":43,"text":492},{"id":713,"depth":43,"text":714},{"id":802,"depth":43,"text":803},"integrations","cURL extension, Guzzle, or Laravel's HTTP client — render PDFs with retries and proper error handling.","md",{},"\u002Fdocs\u002Fguides\u002Fintegrations\u002Fphp-laravel",{"title":5,"description":838},"docs\u002Fguides\u002Fintegrations\u002Fphp-laravel","PeBRpKsCFoCAjJcnkSnK9uWAQqF1bjYInIz10q4F9Ak",[846,851,855,859,863,867,871,875,880,884,888,892,897,901,902,906,910],{"path":847,"title":848,"description":849,"category":850,"order":62},"\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":852,"title":853,"description":854,"category":850,"order":50},"\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":856,"title":857,"description":858,"category":850,"order":43},"\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":860,"title":861,"description":862,"category":850,"order":56},"\u002Fdocs\u002Fguides\u002Fai-mcp\u002Fcursor","Cursor","Wire Transactional into Cursor's MCP support so you can generate PDFs from inside your editor.",{"path":864,"title":865,"description":866,"category":850,"order":68},"\u002Fdocs\u002Fguides\u002Fai-mcp\u002Fgemini","Gemini Code Assist \u002F Gemini CLI","Connect Transactional to Google's Gemini agents through their MCP support.",{"path":868,"title":869,"description":870,"category":850,"order":74},"\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":872,"title":873,"description":874,"category":850,"order":37},"\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":876,"title":877,"description":878,"category":879,"order":43},"\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":881,"title":882,"description":883,"category":879,"order":37},"\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":885,"title":886,"description":887,"category":879,"order":50},"\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":889,"title":890,"description":891,"category":879,"order":56},"\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":893,"title":894,"description":895,"category":896,"order":37},"\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":898,"title":899,"description":900,"category":837,"order":37},"\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":841,"title":5,"description":838,"category":837,"order":43},{"path":903,"title":904,"description":905,"category":837,"order":50},"\u002Fdocs\u002Fguides\u002Fintegrations\u002Fpython","Calling \u002Fv1\u002Fgenerate from Python","urllib (stdlib), requests, or httpx with retry — render PDFs from Django, FastAPI, or any Python service.",{"path":823,"title":907,"description":908,"category":909,"order":37},"Monitoring usage & credits","Read the dashboard gauges, set sane alerts, and decide when to top up vs. upgrade.","operations",{"path":815,"title":911,"description":912,"category":909,"order":43},"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.",1780347733849]