[{"data":1,"prerenderedAt":1820},["ShallowReactive",2],{"guide-\u002Fdocs\u002Fguides\u002Fintegrations\u002Fnode-bun":3,"guides-all":1752},{"id":4,"title":5,"body":6,"category":1743,"description":1744,"extension":1745,"icon":1746,"meta":1747,"navigation":213,"order":41,"path":1748,"seo":1749,"stem":1750,"__hash__":1751},"guides\u002Fdocs\u002Fguides\u002Fintegrations\u002Fnode-bun.md","Calling \u002Fv1\u002Fgenerate from Node.js & Bun",{"type":7,"value":8,"toc":1735},"minimark",[9,14,23,28,401,405,412,432,1119,1123,1136,1334,1338,1344,1505,1508,1634,1638,1648,1685,1706,1710,1731],[10,11,13],"h1",{"id":12},"nodejs-bun","Node.js & Bun",[15,16,17,18,22],"p",{},"The Transactional client API is a single POST. Native ",[19,20,21],"code",{},"fetch"," (Node 18+, Deno, Bun, browsers) is enough — no SDK to install.",[24,25,27],"h2",{"id":26},"minimal-example","Minimal example",[29,30,35],"pre",{"className":31,"code":32,"language":33,"meta":34,"style":34},"language-ts shiki shiki-themes github-light github-dark","const res = await fetch('https:\u002F\u002Fapi.transactional.dev\u002Fv1\u002Fgenerate', {\n  method: 'POST',\n  headers: {\n    'x-api-token': process.env.TRANSACTIONAL_API_TOKEN!,\n    'Content-Type': 'application\u002Fjson',\n  },\n  body: JSON.stringify({\n    documentId: '1d8e5d56-1a4f-4b62-8c33-2d34a64b2f00',\n    variables: {\n      customer: {name: 'Acme Corp'},\n      invoice: {number: 'INV-2026-0142', total: 1280.50},\n    },\n  }),\n})\n\nif (!res.ok) {\n  const err = await res.json() as {error: string; message: string}\n  throw new Error(`Transactional ${res.status}: ${err.error} — ${err.message}`)\n}\n\nconst {url} = await res.json() as {url: string; documentId: string}\nconsole.log(url) \u002F\u002F signed, short-lived\n","ts","",[19,36,37,71,83,89,106,120,126,144,155,161,173,190,196,202,208,215,229,280,330,335,340,385],{"__ignoreMap":34},[38,39,42,46,50,53,56,60,64,68],"span",{"class":40,"line":41},"line",1,[38,43,45],{"class":44},"szBVR","const",[38,47,49],{"class":48},"sj4cs"," res",[38,51,52],{"class":44}," =",[38,54,55],{"class":44}," await",[38,57,59],{"class":58},"sScJk"," fetch",[38,61,63],{"class":62},"sVt8B","(",[38,65,67],{"class":66},"sZZnC","'https:\u002F\u002Fapi.transactional.dev\u002Fv1\u002Fgenerate'",[38,69,70],{"class":62},", {\n",[38,72,74,77,80],{"class":40,"line":73},2,[38,75,76],{"class":62},"  method: ",[38,78,79],{"class":66},"'POST'",[38,81,82],{"class":62},",\n",[38,84,86],{"class":40,"line":85},3,[38,87,88],{"class":62},"  headers: {\n",[38,90,92,95,98,101,104],{"class":40,"line":91},4,[38,93,94],{"class":66},"    'x-api-token'",[38,96,97],{"class":62},": process.env.",[38,99,100],{"class":48},"TRANSACTIONAL_API_TOKEN",[38,102,103],{"class":44},"!",[38,105,82],{"class":62},[38,107,109,112,115,118],{"class":40,"line":108},5,[38,110,111],{"class":66},"    'Content-Type'",[38,113,114],{"class":62},": ",[38,116,117],{"class":66},"'application\u002Fjson'",[38,119,82],{"class":62},[38,121,123],{"class":40,"line":122},6,[38,124,125],{"class":62},"  },\n",[38,127,129,132,135,138,141],{"class":40,"line":128},7,[38,130,131],{"class":62},"  body: ",[38,133,134],{"class":48},"JSON",[38,136,137],{"class":62},".",[38,139,140],{"class":58},"stringify",[38,142,143],{"class":62},"({\n",[38,145,147,150,153],{"class":40,"line":146},8,[38,148,149],{"class":62},"    documentId: ",[38,151,152],{"class":66},"'1d8e5d56-1a4f-4b62-8c33-2d34a64b2f00'",[38,154,82],{"class":62},[38,156,158],{"class":40,"line":157},9,[38,159,160],{"class":62},"    variables: {\n",[38,162,164,167,170],{"class":40,"line":163},10,[38,165,166],{"class":62},"      customer: {name: ",[38,168,169],{"class":66},"'Acme Corp'",[38,171,172],{"class":62},"},\n",[38,174,176,179,182,185,188],{"class":40,"line":175},11,[38,177,178],{"class":62},"      invoice: {number: ",[38,180,181],{"class":66},"'INV-2026-0142'",[38,183,184],{"class":62},", total: ",[38,186,187],{"class":48},"1280.50",[38,189,172],{"class":62},[38,191,193],{"class":40,"line":192},12,[38,194,195],{"class":62},"    },\n",[38,197,199],{"class":40,"line":198},13,[38,200,201],{"class":62},"  }),\n",[38,203,205],{"class":40,"line":204},14,[38,206,207],{"class":62},"})\n",[38,209,211],{"class":40,"line":210},15,[38,212,214],{"emptyLinePlaceholder":213},true,"\n",[38,216,218,221,224,226],{"class":40,"line":217},16,[38,219,220],{"class":44},"if",[38,222,223],{"class":62}," (",[38,225,103],{"class":44},[38,227,228],{"class":62},"res.ok) {\n",[38,230,232,235,238,240,242,245,248,251,254,257,261,264,267,270,273,275,277],{"class":40,"line":231},17,[38,233,234],{"class":44},"  const",[38,236,237],{"class":48}," err",[38,239,52],{"class":44},[38,241,55],{"class":44},[38,243,244],{"class":62}," res.",[38,246,247],{"class":58},"json",[38,249,250],{"class":62},"() ",[38,252,253],{"class":44},"as",[38,255,256],{"class":62}," {",[38,258,260],{"class":259},"s4XuR","error",[38,262,263],{"class":44},":",[38,265,266],{"class":48}," string",[38,268,269],{"class":62},"; ",[38,271,272],{"class":259},"message",[38,274,263],{"class":44},[38,276,266],{"class":48},[38,278,279],{"class":62},"}\n",[38,281,283,286,289,292,294,297,300,302,305,308,311,313,315,318,320,322,324,327],{"class":40,"line":282},18,[38,284,285],{"class":44},"  throw",[38,287,288],{"class":44}," new",[38,290,291],{"class":58}," Error",[38,293,63],{"class":62},[38,295,296],{"class":66},"`Transactional ${",[38,298,299],{"class":62},"res",[38,301,137],{"class":66},[38,303,304],{"class":62},"status",[38,306,307],{"class":66},"}: ${",[38,309,310],{"class":62},"err",[38,312,137],{"class":66},[38,314,260],{"class":62},[38,316,317],{"class":66},"} — ${",[38,319,310],{"class":62},[38,321,137],{"class":66},[38,323,272],{"class":62},[38,325,326],{"class":66},"}`",[38,328,329],{"class":62},")\n",[38,331,333],{"class":40,"line":332},19,[38,334,279],{"class":62},[38,336,338],{"class":40,"line":337},20,[38,339,214],{"emptyLinePlaceholder":213},[38,341,343,345,347,350,353,356,358,360,362,364,366,368,370,372,374,376,379,381,383],{"class":40,"line":342},21,[38,344,45],{"class":44},[38,346,256],{"class":62},[38,348,349],{"class":48},"url",[38,351,352],{"class":62},"} ",[38,354,355],{"class":44},"=",[38,357,55],{"class":44},[38,359,244],{"class":62},[38,361,247],{"class":58},[38,363,250],{"class":62},[38,365,253],{"class":44},[38,367,256],{"class":62},[38,369,349],{"class":259},[38,371,263],{"class":44},[38,373,266],{"class":48},[38,375,269],{"class":62},[38,377,378],{"class":259},"documentId",[38,380,263],{"class":44},[38,382,266],{"class":48},[38,384,279],{"class":62},[38,386,388,391,394,397],{"class":40,"line":387},22,[38,389,390],{"class":62},"console.",[38,392,393],{"class":58},"log",[38,395,396],{"class":62},"(url) ",[38,398,400],{"class":399},"sJ8bj","\u002F\u002F signed, short-lived\n",[24,402,404],{"id":403},"production-grade-wrapper","Production-grade wrapper",[15,406,407,408,411],{},"The endpoints are idempotent — the same ",[19,409,410],{},"documentId + variables"," always yields the same PDF (modulo upload timestamp in the URL). That makes retries safe. Two transient cases are worth handling:",[413,414,415,426],"ul",{},[416,417,418,425],"li",{},[419,420,421,422],"strong",{},"503 ",[19,423,424],{},"storage_unavailable"," — S3 hiccup. The credit has already been refunded on our side. Retry once.",[416,427,428,431],{},[419,429,430],{},"Network errors"," — TCP reset, DNS blip. Retry with backoff.",[29,433,435],{"className":31,"code":434,"language":33,"meta":34,"style":34},"interface GenerateInput {\n  documentId: string\n  variables?: Record\u003Cstring, unknown>\n}\n\ninterface GenerateOk {\n  url: string\n  documentId: string\n}\n\ninterface TransactionalError extends Error {\n  status: number\n  code: string\n}\n\nasync function generatePdf(input: GenerateInput, opts: {maxRetries?: number} = {}): Promise\u003CGenerateOk> {\n  const maxRetries = opts.maxRetries ?? 2\n  let lastErr: unknown\n\n  for (let attempt = 0; attempt \u003C= maxRetries; attempt++) {\n    try {\n      const res = await fetch('https:\u002F\u002Fapi.transactional.dev\u002Fv1\u002Fgenerate', {\n        method: 'POST',\n        headers: {\n          'x-api-token': process.env.TRANSACTIONAL_API_TOKEN!,\n          'Content-Type': 'application\u002Fjson',\n        },\n        body: JSON.stringify(input),\n      })\n\n      if (res.ok) return (await res.json()) as GenerateOk\n\n      const body = (await res.json().catch(() => ({}))) as {error?: string; message?: string}\n      const err = Object.assign(new Error(body.message ?? res.statusText), {\n        status: res.status,\n        code: body.error ?? 'unknown',\n      }) as TransactionalError\n\n      \u002F\u002F 4xx (except 429) won't change — fail fast.\n      if (res.status >= 400 && res.status \u003C 500 && res.status !== 429) throw err\n\n      \u002F\u002F 429, 5xx — retry with exponential backoff.\n      lastErr = err\n    } catch (err) {\n      lastErr = err\n    }\n\n    if (attempt \u003C maxRetries) await new Promise(r => setTimeout(r, 200 * 2 ** attempt))\n  }\n\n  throw lastErr\n}\n",[19,436,437,448,458,484,488,492,501,510,518,522,526,540,550,559,563,567,624,642,655,659,692,699,718,728,734,748,760,766,781,787,792,821,826,879,909,915,928,939,944,950,994,999,1005,1015,1026,1035,1041,1046,1095,1101,1106,1114],{"__ignoreMap":34},[38,438,439,442,445],{"class":40,"line":41},[38,440,441],{"class":44},"interface",[38,443,444],{"class":58}," GenerateInput",[38,446,447],{"class":62}," {\n",[38,449,450,453,455],{"class":40,"line":73},[38,451,452],{"class":259},"  documentId",[38,454,263],{"class":44},[38,456,457],{"class":48}," string\n",[38,459,460,463,466,469,472,475,478,481],{"class":40,"line":85},[38,461,462],{"class":259},"  variables",[38,464,465],{"class":44},"?:",[38,467,468],{"class":58}," Record",[38,470,471],{"class":62},"\u003C",[38,473,474],{"class":48},"string",[38,476,477],{"class":62},", ",[38,479,480],{"class":48},"unknown",[38,482,483],{"class":62},">\n",[38,485,486],{"class":40,"line":91},[38,487,279],{"class":62},[38,489,490],{"class":40,"line":108},[38,491,214],{"emptyLinePlaceholder":213},[38,493,494,496,499],{"class":40,"line":122},[38,495,441],{"class":44},[38,497,498],{"class":58}," GenerateOk",[38,500,447],{"class":62},[38,502,503,506,508],{"class":40,"line":128},[38,504,505],{"class":259},"  url",[38,507,263],{"class":44},[38,509,457],{"class":48},[38,511,512,514,516],{"class":40,"line":146},[38,513,452],{"class":259},[38,515,263],{"class":44},[38,517,457],{"class":48},[38,519,520],{"class":40,"line":157},[38,521,279],{"class":62},[38,523,524],{"class":40,"line":163},[38,525,214],{"emptyLinePlaceholder":213},[38,527,528,530,533,536,538],{"class":40,"line":175},[38,529,441],{"class":44},[38,531,532],{"class":58}," TransactionalError",[38,534,535],{"class":44}," extends",[38,537,291],{"class":58},[38,539,447],{"class":62},[38,541,542,545,547],{"class":40,"line":192},[38,543,544],{"class":259},"  status",[38,546,263],{"class":44},[38,548,549],{"class":48}," number\n",[38,551,552,555,557],{"class":40,"line":198},[38,553,554],{"class":259},"  code",[38,556,263],{"class":44},[38,558,457],{"class":48},[38,560,561],{"class":40,"line":204},[38,562,279],{"class":62},[38,564,565],{"class":40,"line":210},[38,566,214],{"emptyLinePlaceholder":213},[38,568,569,572,575,578,580,583,585,587,589,592,594,596,599,601,604,606,608,611,613,616,618,621],{"class":40,"line":217},[38,570,571],{"class":44},"async",[38,573,574],{"class":44}," function",[38,576,577],{"class":58}," generatePdf",[38,579,63],{"class":62},[38,581,582],{"class":259},"input",[38,584,263],{"class":44},[38,586,444],{"class":58},[38,588,477],{"class":62},[38,590,591],{"class":259},"opts",[38,593,263],{"class":44},[38,595,256],{"class":62},[38,597,598],{"class":259},"maxRetries",[38,600,465],{"class":44},[38,602,603],{"class":48}," number",[38,605,352],{"class":62},[38,607,355],{"class":44},[38,609,610],{"class":62}," {})",[38,612,263],{"class":44},[38,614,615],{"class":58}," Promise",[38,617,471],{"class":62},[38,619,620],{"class":58},"GenerateOk",[38,622,623],{"class":62},"> {\n",[38,625,626,628,631,633,636,639],{"class":40,"line":231},[38,627,234],{"class":44},[38,629,630],{"class":48}," maxRetries",[38,632,52],{"class":44},[38,634,635],{"class":62}," opts.maxRetries ",[38,637,638],{"class":44},"??",[38,640,641],{"class":48}," 2\n",[38,643,644,647,650,652],{"class":40,"line":282},[38,645,646],{"class":44},"  let",[38,648,649],{"class":62}," lastErr",[38,651,263],{"class":44},[38,653,654],{"class":48}," unknown\n",[38,656,657],{"class":40,"line":332},[38,658,214],{"emptyLinePlaceholder":213},[38,660,661,664,666,669,672,674,677,680,683,686,689],{"class":40,"line":337},[38,662,663],{"class":44},"  for",[38,665,223],{"class":62},[38,667,668],{"class":44},"let",[38,670,671],{"class":62}," attempt ",[38,673,355],{"class":44},[38,675,676],{"class":48}," 0",[38,678,679],{"class":62},"; attempt ",[38,681,682],{"class":44},"\u003C=",[38,684,685],{"class":62}," maxRetries; attempt",[38,687,688],{"class":44},"++",[38,690,691],{"class":62},") {\n",[38,693,694,697],{"class":40,"line":342},[38,695,696],{"class":44},"    try",[38,698,447],{"class":62},[38,700,701,704,706,708,710,712,714,716],{"class":40,"line":387},[38,702,703],{"class":44},"      const",[38,705,49],{"class":48},[38,707,52],{"class":44},[38,709,55],{"class":44},[38,711,59],{"class":58},[38,713,63],{"class":62},[38,715,67],{"class":66},[38,717,70],{"class":62},[38,719,721,724,726],{"class":40,"line":720},23,[38,722,723],{"class":62},"        method: ",[38,725,79],{"class":66},[38,727,82],{"class":62},[38,729,731],{"class":40,"line":730},24,[38,732,733],{"class":62},"        headers: {\n",[38,735,737,740,742,744,746],{"class":40,"line":736},25,[38,738,739],{"class":66},"          'x-api-token'",[38,741,97],{"class":62},[38,743,100],{"class":48},[38,745,103],{"class":44},[38,747,82],{"class":62},[38,749,751,754,756,758],{"class":40,"line":750},26,[38,752,753],{"class":66},"          'Content-Type'",[38,755,114],{"class":62},[38,757,117],{"class":66},[38,759,82],{"class":62},[38,761,763],{"class":40,"line":762},27,[38,764,765],{"class":62},"        },\n",[38,767,769,772,774,776,778],{"class":40,"line":768},28,[38,770,771],{"class":62},"        body: ",[38,773,134],{"class":48},[38,775,137],{"class":62},[38,777,140],{"class":58},[38,779,780],{"class":62},"(input),\n",[38,782,784],{"class":40,"line":783},29,[38,785,786],{"class":62},"      })\n",[38,788,790],{"class":40,"line":789},30,[38,791,214],{"emptyLinePlaceholder":213},[38,793,795,798,801,804,806,809,811,813,816,818],{"class":40,"line":794},31,[38,796,797],{"class":44},"      if",[38,799,800],{"class":62}," (res.ok) ",[38,802,803],{"class":44},"return",[38,805,223],{"class":62},[38,807,808],{"class":44},"await",[38,810,244],{"class":62},[38,812,247],{"class":58},[38,814,815],{"class":62},"()) ",[38,817,253],{"class":44},[38,819,820],{"class":58}," GenerateOk\n",[38,822,824],{"class":40,"line":823},32,[38,825,214],{"emptyLinePlaceholder":213},[38,827,829,831,834,836,838,840,842,844,847,850,853,856,859,861,863,865,867,869,871,873,875,877],{"class":40,"line":828},33,[38,830,703],{"class":44},[38,832,833],{"class":48}," body",[38,835,52],{"class":44},[38,837,223],{"class":62},[38,839,808],{"class":44},[38,841,244],{"class":62},[38,843,247],{"class":58},[38,845,846],{"class":62},"().",[38,848,849],{"class":58},"catch",[38,851,852],{"class":62},"(() ",[38,854,855],{"class":44},"=>",[38,857,858],{"class":62}," ({}))) ",[38,860,253],{"class":44},[38,862,256],{"class":62},[38,864,260],{"class":259},[38,866,465],{"class":44},[38,868,266],{"class":48},[38,870,269],{"class":62},[38,872,272],{"class":259},[38,874,465],{"class":44},[38,876,266],{"class":48},[38,878,279],{"class":62},[38,880,882,884,886,888,891,894,896,899,901,904,906],{"class":40,"line":881},34,[38,883,703],{"class":44},[38,885,237],{"class":48},[38,887,52],{"class":44},[38,889,890],{"class":62}," Object.",[38,892,893],{"class":58},"assign",[38,895,63],{"class":62},[38,897,898],{"class":44},"new",[38,900,291],{"class":58},[38,902,903],{"class":62},"(body.message ",[38,905,638],{"class":44},[38,907,908],{"class":62}," res.statusText), {\n",[38,910,912],{"class":40,"line":911},35,[38,913,914],{"class":62},"        status: res.status,\n",[38,916,918,921,923,926],{"class":40,"line":917},36,[38,919,920],{"class":62},"        code: body.error ",[38,922,638],{"class":44},[38,924,925],{"class":66}," 'unknown'",[38,927,82],{"class":62},[38,929,931,934,936],{"class":40,"line":930},37,[38,932,933],{"class":62},"      }) ",[38,935,253],{"class":44},[38,937,938],{"class":58}," TransactionalError\n",[38,940,942],{"class":40,"line":941},38,[38,943,214],{"emptyLinePlaceholder":213},[38,945,947],{"class":40,"line":946},39,[38,948,949],{"class":399},"      \u002F\u002F 4xx (except 429) won't change — fail fast.\n",[38,951,953,955,958,961,964,967,970,972,975,977,979,982,985,988,991],{"class":40,"line":952},40,[38,954,797],{"class":44},[38,956,957],{"class":62}," (res.status ",[38,959,960],{"class":44},">=",[38,962,963],{"class":48}," 400",[38,965,966],{"class":44}," &&",[38,968,969],{"class":62}," res.status ",[38,971,471],{"class":44},[38,973,974],{"class":48}," 500",[38,976,966],{"class":44},[38,978,969],{"class":62},[38,980,981],{"class":44},"!==",[38,983,984],{"class":48}," 429",[38,986,987],{"class":62},") ",[38,989,990],{"class":44},"throw",[38,992,993],{"class":62}," err\n",[38,995,997],{"class":40,"line":996},41,[38,998,214],{"emptyLinePlaceholder":213},[38,1000,1002],{"class":40,"line":1001},42,[38,1003,1004],{"class":399},"      \u002F\u002F 429, 5xx — retry with exponential backoff.\n",[38,1006,1008,1011,1013],{"class":40,"line":1007},43,[38,1009,1010],{"class":62},"      lastErr ",[38,1012,355],{"class":44},[38,1014,993],{"class":62},[38,1016,1018,1021,1023],{"class":40,"line":1017},44,[38,1019,1020],{"class":62},"    } ",[38,1022,849],{"class":44},[38,1024,1025],{"class":62}," (err) {\n",[38,1027,1029,1031,1033],{"class":40,"line":1028},45,[38,1030,1010],{"class":62},[38,1032,355],{"class":44},[38,1034,993],{"class":62},[38,1036,1038],{"class":40,"line":1037},46,[38,1039,1040],{"class":62},"    }\n",[38,1042,1044],{"class":40,"line":1043},47,[38,1045,214],{"emptyLinePlaceholder":213},[38,1047,1049,1052,1055,1057,1060,1062,1064,1066,1068,1071,1074,1077,1080,1083,1086,1089,1092],{"class":40,"line":1048},48,[38,1050,1051],{"class":44},"    if",[38,1053,1054],{"class":62}," (attempt ",[38,1056,471],{"class":44},[38,1058,1059],{"class":62}," maxRetries) ",[38,1061,808],{"class":44},[38,1063,288],{"class":44},[38,1065,615],{"class":48},[38,1067,63],{"class":62},[38,1069,1070],{"class":259},"r",[38,1072,1073],{"class":44}," =>",[38,1075,1076],{"class":58}," setTimeout",[38,1078,1079],{"class":62},"(r, ",[38,1081,1082],{"class":48},"200",[38,1084,1085],{"class":44}," *",[38,1087,1088],{"class":48}," 2",[38,1090,1091],{"class":44}," **",[38,1093,1094],{"class":62}," attempt))\n",[38,1096,1098],{"class":40,"line":1097},49,[38,1099,1100],{"class":62},"  }\n",[38,1102,1104],{"class":40,"line":1103},50,[38,1105,214],{"emptyLinePlaceholder":213},[38,1107,1109,1111],{"class":40,"line":1108},51,[38,1110,285],{"class":44},[38,1112,1113],{"class":62}," lastErr\n",[38,1115,1117],{"class":40,"line":1116},52,[38,1118,279],{"class":62},[24,1120,1122],{"id":1121},"branching-on-error-codes","Branching on error codes",[15,1124,1125,1126,1128,1129,1131,1132,1135],{},"Branch on ",[19,1127,260],{}," (stable machine-readable code), never on ",[19,1130,272],{}," (localized via ",[19,1133,1134],{},"Accept-Language"," and may change wording).",[29,1137,1139],{"className":31,"code":1138,"language":33,"meta":34,"style":34},"try {\n  const {url} = await generatePdf({documentId, variables})\n  return url\n} catch (err) {\n  const e = err as TransactionalError\n  switch (e.code) {\n    case 'quota_exceeded':\n      \u002F\u002F 402 — out of credits. Surface to the user, link to billing.\n      throw new UserFacingError('Out of PDF credits this month.')\n    case 'NOT_FOUND':\n    case 'invalid_document_id':\n      \u002F\u002F The documentId is wrong — programmer error, not a user issue.\n      throw new ProgrammerError(`Bad template: ${documentId}`)\n    case 'UNAUTHORIZED':\n      \u002F\u002F Token revoked. Page oncall.\n      throw new ConfigError('TRANSACTIONAL_API_TOKEN is invalid.')\n    default:\n      throw err\n  }\n}\n",[19,1140,1141,1148,1167,1175,1183,1199,1207,1218,1223,1240,1249,1258,1263,1283,1292,1297,1313,1320,1326,1330],{"__ignoreMap":34},[38,1142,1143,1146],{"class":40,"line":41},[38,1144,1145],{"class":44},"try",[38,1147,447],{"class":62},[38,1149,1150,1152,1154,1156,1158,1160,1162,1164],{"class":40,"line":73},[38,1151,234],{"class":44},[38,1153,256],{"class":62},[38,1155,349],{"class":48},[38,1157,352],{"class":62},[38,1159,355],{"class":44},[38,1161,55],{"class":44},[38,1163,577],{"class":58},[38,1165,1166],{"class":62},"({documentId, variables})\n",[38,1168,1169,1172],{"class":40,"line":85},[38,1170,1171],{"class":44},"  return",[38,1173,1174],{"class":62}," url\n",[38,1176,1177,1179,1181],{"class":40,"line":91},[38,1178,352],{"class":62},[38,1180,849],{"class":44},[38,1182,1025],{"class":62},[38,1184,1185,1187,1190,1192,1195,1197],{"class":40,"line":108},[38,1186,234],{"class":44},[38,1188,1189],{"class":48}," e",[38,1191,52],{"class":44},[38,1193,1194],{"class":62}," err ",[38,1196,253],{"class":44},[38,1198,938],{"class":58},[38,1200,1201,1204],{"class":40,"line":122},[38,1202,1203],{"class":44},"  switch",[38,1205,1206],{"class":62}," (e.code) {\n",[38,1208,1209,1212,1215],{"class":40,"line":128},[38,1210,1211],{"class":44},"    case",[38,1213,1214],{"class":66}," 'quota_exceeded'",[38,1216,1217],{"class":62},":\n",[38,1219,1220],{"class":40,"line":146},[38,1221,1222],{"class":399},"      \u002F\u002F 402 — out of credits. Surface to the user, link to billing.\n",[38,1224,1225,1228,1230,1233,1235,1238],{"class":40,"line":157},[38,1226,1227],{"class":44},"      throw",[38,1229,288],{"class":44},[38,1231,1232],{"class":58}," UserFacingError",[38,1234,63],{"class":62},[38,1236,1237],{"class":66},"'Out of PDF credits this month.'",[38,1239,329],{"class":62},[38,1241,1242,1244,1247],{"class":40,"line":163},[38,1243,1211],{"class":44},[38,1245,1246],{"class":66}," 'NOT_FOUND'",[38,1248,1217],{"class":62},[38,1250,1251,1253,1256],{"class":40,"line":175},[38,1252,1211],{"class":44},[38,1254,1255],{"class":66}," 'invalid_document_id'",[38,1257,1217],{"class":62},[38,1259,1260],{"class":40,"line":192},[38,1261,1262],{"class":399},"      \u002F\u002F The documentId is wrong — programmer error, not a user issue.\n",[38,1264,1265,1267,1269,1272,1274,1277,1279,1281],{"class":40,"line":198},[38,1266,1227],{"class":44},[38,1268,288],{"class":44},[38,1270,1271],{"class":58}," ProgrammerError",[38,1273,63],{"class":62},[38,1275,1276],{"class":66},"`Bad template: ${",[38,1278,378],{"class":62},[38,1280,326],{"class":66},[38,1282,329],{"class":62},[38,1284,1285,1287,1290],{"class":40,"line":204},[38,1286,1211],{"class":44},[38,1288,1289],{"class":66}," 'UNAUTHORIZED'",[38,1291,1217],{"class":62},[38,1293,1294],{"class":40,"line":210},[38,1295,1296],{"class":399},"      \u002F\u002F Token revoked. Page oncall.\n",[38,1298,1299,1301,1303,1306,1308,1311],{"class":40,"line":217},[38,1300,1227],{"class":44},[38,1302,288],{"class":44},[38,1304,1305],{"class":58}," ConfigError",[38,1307,63],{"class":62},[38,1309,1310],{"class":66},"'TRANSACTIONAL_API_TOKEN is invalid.'",[38,1312,329],{"class":62},[38,1314,1315,1318],{"class":40,"line":231},[38,1316,1317],{"class":44},"    default",[38,1319,1217],{"class":62},[38,1321,1322,1324],{"class":40,"line":282},[38,1323,1227],{"class":44},[38,1325,993],{"class":62},[38,1327,1328],{"class":40,"line":332},[38,1329,1100],{"class":62},[38,1331,1332],{"class":40,"line":337},[38,1333,279],{"class":62},[24,1335,1337],{"id":1336},"streaming-the-pdf-to-your-storage","Streaming the PDF to your storage",[15,1339,1340,1341,1343],{},"The returned ",[19,1342,349],{}," is signed and short-lived. Don't hand it to end users directly unless your UX is \"click here, downloads immediately\". For long-lived storage, pipe it through your own bucket:",[29,1345,1347],{"className":31,"code":1346,"language":33,"meta":34,"style":34},"import {createWriteStream} from 'node:fs'\nimport {pipeline} from 'node:stream\u002Fpromises'\n\nconst {url} = await generatePdf({documentId, variables})\n\nconst pdfRes = await fetch(url)\nif (!pdfRes.ok || !pdfRes.body) throw new Error(`PDF download failed: ${pdfRes.status}`)\n\nawait pipeline(pdfRes.body as any, createWriteStream(`\u002Ftmp\u002Finvoice-${Date.now()}.pdf`))\n",[19,1348,1349,1363,1375,1379,1397,1401,1417,1459,1463],{"__ignoreMap":34},[38,1350,1351,1354,1357,1360],{"class":40,"line":41},[38,1352,1353],{"class":44},"import",[38,1355,1356],{"class":62}," {createWriteStream} ",[38,1358,1359],{"class":44},"from",[38,1361,1362],{"class":66}," 'node:fs'\n",[38,1364,1365,1367,1370,1372],{"class":40,"line":73},[38,1366,1353],{"class":44},[38,1368,1369],{"class":62}," {pipeline} ",[38,1371,1359],{"class":44},[38,1373,1374],{"class":66}," 'node:stream\u002Fpromises'\n",[38,1376,1377],{"class":40,"line":85},[38,1378,214],{"emptyLinePlaceholder":213},[38,1380,1381,1383,1385,1387,1389,1391,1393,1395],{"class":40,"line":91},[38,1382,45],{"class":44},[38,1384,256],{"class":62},[38,1386,349],{"class":48},[38,1388,352],{"class":62},[38,1390,355],{"class":44},[38,1392,55],{"class":44},[38,1394,577],{"class":58},[38,1396,1166],{"class":62},[38,1398,1399],{"class":40,"line":108},[38,1400,214],{"emptyLinePlaceholder":213},[38,1402,1403,1405,1408,1410,1412,1414],{"class":40,"line":122},[38,1404,45],{"class":44},[38,1406,1407],{"class":48}," pdfRes",[38,1409,52],{"class":44},[38,1411,55],{"class":44},[38,1413,59],{"class":58},[38,1415,1416],{"class":62},"(url)\n",[38,1418,1419,1421,1423,1425,1428,1431,1434,1437,1439,1441,1443,1445,1448,1451,1453,1455,1457],{"class":40,"line":128},[38,1420,220],{"class":44},[38,1422,223],{"class":62},[38,1424,103],{"class":44},[38,1426,1427],{"class":62},"pdfRes.ok ",[38,1429,1430],{"class":44},"||",[38,1432,1433],{"class":44}," !",[38,1435,1436],{"class":62},"pdfRes.body) ",[38,1438,990],{"class":44},[38,1440,288],{"class":44},[38,1442,291],{"class":58},[38,1444,63],{"class":62},[38,1446,1447],{"class":66},"`PDF download failed: ${",[38,1449,1450],{"class":62},"pdfRes",[38,1452,137],{"class":66},[38,1454,304],{"class":62},[38,1456,326],{"class":66},[38,1458,329],{"class":62},[38,1460,1461],{"class":40,"line":146},[38,1462,214],{"emptyLinePlaceholder":213},[38,1464,1465,1467,1470,1473,1475,1478,1480,1483,1485,1488,1491,1493,1496,1499,1502],{"class":40,"line":157},[38,1466,808],{"class":44},[38,1468,1469],{"class":58}," pipeline",[38,1471,1472],{"class":62},"(pdfRes.body ",[38,1474,253],{"class":44},[38,1476,1477],{"class":48}," any",[38,1479,477],{"class":62},[38,1481,1482],{"class":58},"createWriteStream",[38,1484,63],{"class":62},[38,1486,1487],{"class":66},"`\u002Ftmp\u002Finvoice-${",[38,1489,1490],{"class":62},"Date",[38,1492,137],{"class":66},[38,1494,1495],{"class":58},"now",[38,1497,1498],{"class":66},"()",[38,1500,1501],{"class":66},"}.pdf`",[38,1503,1504],{"class":62},"))\n",[15,1506,1507],{},"Or pipe directly to S3 with the AWS SDK:",[29,1509,1511],{"className":31,"code":1510,"language":33,"meta":34,"style":34},"import {S3Client, PutObjectCommand} from '@aws-sdk\u002Fclient-s3'\n\nconst pdfRes = await fetch(url)\nconst buf = Buffer.from(await pdfRes.arrayBuffer())\nawait s3.send(new PutObjectCommand({\n  Bucket: 'invoices',\n  Key: `2026\u002F05\u002F${invoiceNumber}.pdf`,\n  Body: buf,\n  ContentType: 'application\u002Fpdf',\n}))\n",[19,1512,1513,1525,1529,1543,1570,1589,1599,1614,1619,1629],{"__ignoreMap":34},[38,1514,1515,1517,1520,1522],{"class":40,"line":41},[38,1516,1353],{"class":44},[38,1518,1519],{"class":62}," {S3Client, PutObjectCommand} ",[38,1521,1359],{"class":44},[38,1523,1524],{"class":66}," '@aws-sdk\u002Fclient-s3'\n",[38,1526,1527],{"class":40,"line":73},[38,1528,214],{"emptyLinePlaceholder":213},[38,1530,1531,1533,1535,1537,1539,1541],{"class":40,"line":85},[38,1532,45],{"class":44},[38,1534,1407],{"class":48},[38,1536,52],{"class":44},[38,1538,55],{"class":44},[38,1540,59],{"class":58},[38,1542,1416],{"class":62},[38,1544,1545,1547,1550,1552,1555,1557,1559,1561,1564,1567],{"class":40,"line":91},[38,1546,45],{"class":44},[38,1548,1549],{"class":48}," buf",[38,1551,52],{"class":44},[38,1553,1554],{"class":62}," Buffer.",[38,1556,1359],{"class":58},[38,1558,63],{"class":62},[38,1560,808],{"class":44},[38,1562,1563],{"class":62}," pdfRes.",[38,1565,1566],{"class":58},"arrayBuffer",[38,1568,1569],{"class":62},"())\n",[38,1571,1572,1574,1577,1580,1582,1584,1587],{"class":40,"line":108},[38,1573,808],{"class":44},[38,1575,1576],{"class":62}," s3.",[38,1578,1579],{"class":58},"send",[38,1581,63],{"class":62},[38,1583,898],{"class":44},[38,1585,1586],{"class":58}," PutObjectCommand",[38,1588,143],{"class":62},[38,1590,1591,1594,1597],{"class":40,"line":122},[38,1592,1593],{"class":62},"  Bucket: ",[38,1595,1596],{"class":66},"'invoices'",[38,1598,82],{"class":62},[38,1600,1601,1604,1607,1610,1612],{"class":40,"line":128},[38,1602,1603],{"class":62},"  Key: ",[38,1605,1606],{"class":66},"`2026\u002F05\u002F${",[38,1608,1609],{"class":62},"invoiceNumber",[38,1611,1501],{"class":66},[38,1613,82],{"class":62},[38,1615,1616],{"class":40,"line":146},[38,1617,1618],{"class":62},"  Body: buf,\n",[38,1620,1621,1624,1627],{"class":40,"line":157},[38,1622,1623],{"class":62},"  ContentType: ",[38,1625,1626],{"class":66},"'application\u002Fpdf'",[38,1628,82],{"class":62},[38,1630,1631],{"class":40,"line":163},[38,1632,1633],{"class":62},"}))\n",[24,1635,1637],{"id":1636},"bun-specific-note","Bun-specific note",[15,1639,1640,1641,1644,1645,263],{},"Everything above runs on Bun without changes. If you want to skip the buffering step, Bun's ",[19,1642,1643],{},"Bun.write"," accepts a ",[19,1646,1647],{},"Response",[29,1649,1651],{"className":31,"code":1650,"language":33,"meta":34,"style":34},"const pdfRes = await fetch(url)\nawait Bun.write('\u002Ftmp\u002Finvoice.pdf', pdfRes)\n",[19,1652,1653,1667],{"__ignoreMap":34},[38,1654,1655,1657,1659,1661,1663,1665],{"class":40,"line":41},[38,1656,45],{"class":44},[38,1658,1407],{"class":48},[38,1660,52],{"class":44},[38,1662,55],{"class":44},[38,1664,59],{"class":58},[38,1666,1416],{"class":62},[38,1668,1669,1671,1674,1677,1679,1682],{"class":40,"line":73},[38,1670,808],{"class":44},[38,1672,1673],{"class":62}," Bun.",[38,1675,1676],{"class":58},"write",[38,1678,63],{"class":62},[38,1680,1681],{"class":66},"'\u002Ftmp\u002Finvoice.pdf'",[38,1683,1684],{"class":62},", pdfRes)\n",[1686,1687,1700],"aside",{"className":1688},[1689,1690,1691,1692,1693,1694,1695,1696,1697,1698,1699],"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,1701,1702,1705],{},[419,1703,1704],{},"Don't log the API token."," Use a logger that redacts headers, or strip the token from any error you throw. A token in a Sentry breadcrumb is a token leaked.",[24,1707,1709],{"id":1708},"next-steps","Next steps",[413,1711,1712,1722],{},[416,1713,1714,1721],{},[419,1715,1716],{},[1717,1718,1720],"a",{"href":1719},"\u002Fdocs\u002Fguides\u002Foperations\u002Fstoring-pdfs","Storing & serving the generated PDF →"," — strategies for keeping PDFs around past the URL expiry.",[416,1723,1724,1730],{},[419,1725,1726],{},[1717,1727,1729],{"href":1728},"\u002Fdocs\u002Fguides\u002Foperations\u002Fmonitoring-usage","Monitoring usage & credits →"," — when to alert your team before you run out.",[1732,1733,1734],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}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":34,"searchDepth":73,"depth":73,"links":1736},[1737,1738,1739,1740,1741,1742],{"id":26,"depth":73,"text":27},{"id":403,"depth":73,"text":404},{"id":1121,"depth":73,"text":1122},{"id":1336,"depth":73,"text":1337},{"id":1636,"depth":73,"text":1637},{"id":1708,"depth":73,"text":1709},"integrations","Production-grade integration using native fetch — retries, error handling, streaming the PDF to your storage.","md","hexagon",{},"\u002Fdocs\u002Fguides\u002Fintegrations\u002Fnode-bun",{"title":5,"description":1744},"docs\u002Fguides\u002Fintegrations\u002Fnode-bun","NpdLdjPdzBtn5_sM5gQe79_MMRoNVhAZ7jmZCe7cY60",[1753,1758,1762,1766,1770,1774,1778,1782,1787,1791,1795,1799,1804,1805,1809,1813,1817],{"path":1754,"title":1755,"description":1756,"category":1757,"order":108},"\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":1759,"title":1760,"description":1761,"category":1757,"order":85},"\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":1763,"title":1764,"description":1765,"category":1757,"order":73},"\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":1767,"title":1768,"description":1769,"category":1757,"order":91},"\u002Fdocs\u002Fguides\u002Fai-mcp\u002Fcursor","Cursor","Wire Transactional into Cursor's MCP support so you can generate PDFs from inside your editor.",{"path":1771,"title":1772,"description":1773,"category":1757,"order":122},"\u002Fdocs\u002Fguides\u002Fai-mcp\u002Fgemini","Gemini Code Assist \u002F Gemini CLI","Connect Transactional to Google's Gemini agents through their MCP support.",{"path":1775,"title":1776,"description":1777,"category":1757,"order":128},"\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":1779,"title":1780,"description":1781,"category":1757,"order":41},"\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":1783,"title":1784,"description":1785,"category":1786,"order":73},"\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":1788,"title":1789,"description":1790,"category":1786,"order":41},"\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":1792,"title":1793,"description":1794,"category":1786,"order":85},"\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":1796,"title":1797,"description":1798,"category":1786,"order":91},"\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":1800,"title":1801,"description":1802,"category":1803,"order":41},"\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":1748,"title":5,"description":1744,"category":1743,"order":41},{"path":1806,"title":1807,"description":1808,"category":1743,"order":73},"\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":1810,"title":1811,"description":1812,"category":1743,"order":85},"\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":1728,"title":1814,"description":1815,"category":1816,"order":41},"Monitoring usage & credits","Read the dashboard gauges, set sane alerts, and decide when to top up vs. upgrade.","operations",{"path":1719,"title":1818,"description":1819,"category":1816,"order":73},"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]