Adding a 'Download PDF' Button Without Exposing Your API Key | PDFly

Board reports, invoices, contracts, certificates, tax receipts. Enterprise customers ask for PDF export often enough that its absence reads as a gap, and the fallback users reach for, screenshotting a dashboard or printing a webpage to PDF, looks exactly as unfinished as it is.

## The architecture that keeps your key safe

Four steps, and the important one is which side of the request holds the credential:

1. Frontend triggers a "Download PDF" action against **your own backend**, not ours 2. Your backend builds the HTML from your database 3. Your backend calls our API, holding the API key server-side the whole time 4. Your backend streams the PDF back to the frontend

The API key never reaches the browser at any point. A key embedded in frontend JavaScript is visible to anyone who opens devtools, and once it is out it is out, there is no way to un-expose it short of revoking the key entirely.

## Frontend

```javascript async function downloadPdf(reportId) { const res = await fetch('/api/generate-report-pdf', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ reportId }), }); const blob = await res.blob(); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'report.pdf'; a.click(); URL.revokeObjectURL(url); } ```

Note the URL: `/api/generate-report-pdf` is a route on your own backend, not ours.

## Backend

```javascript app.post('/api/generate-report-pdf', async (req, res) => { const report = await getReport(req.body.reportId); const html = renderReportHTML(report);

const pdfRes = await fetch('https://pdfly.3idhmind.in/api/pdf/generate', { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${process.env.PDFLY_API_KEY}`, }, body: JSON.stringify({ documents: [{ title: report.title, content: html }], template: 'professional', }), });

const data = await pdfRes.json(); const buffer = Buffer.from(data.documents[0].pdf_base64, 'base64'); res.setHeader('Content-Type', 'application/pdf'); res.send(buffer); }); ```

`process.env.PDFLY_API_KEY` is read on the server, set through your deployment platform's environment variables, never committed and never sent to a client.

## Things worth deciding on purpose

- **Cache the rendered PDF if the source data has not changed.** Regenerating an identical report on every download wastes your monthly document allowance for nothing. - **Match the template to your own branding, not the default.** "professional" is the sensible default but your own product deserves to look like your own product. - **A batch of five documents costs one request against the rate limit, not five.** If your export is naturally batchable, for example exporting every report for a workspace at once, batching is the cheaper way to use the free tier's request allowance; the monthly document quota still counts every document individually. - **If your content has non-Latin scripts,** the API now embeds Devanagari and Arabic fonts automatically. Chinese, Japanese, Korean, Hebrew and Thai still are not supported anywhere in the product, and the response carries a warning rather than failing silently if you send them.

Full request and response shapes, including the storage block that tells you whether the file persists on our end, are on the [API reference](/docs).