@pdfbrew/sdk
v0.2.0
Published
TypeScript SDK for the pdfbrew PDF rendering API
Downloads
122
Maintainers
Readme
@pdfbrew/sdk
TypeScript SDK for pdfbrew — PDF infrastructure for developers.
Render HTML, Markdown, or URLs to pixel-perfect PDFs with a single API call. Cached, async, with webhooks and custom fonts.
Installation
npm install @pdfbrew/sdkQuick start
import { PdfBrew } from '@pdfbrew/sdk'
const client = new PdfBrew('pdfbrew_live_...')
// HTML → PDF
const render = await client.createRender({
source: { type: 'html', html: '<h1>Invoice #42</h1>' },
options: { format: 'A4' },
async: false,
})
console.log(render.output.url) // signed 1-hour download URL
console.log(render.cache_hit) // true if served from cache instantlyRendering
HTML → PDF
const render = await client.createRender({
source: {
type: 'html',
html: '<h1>Hello</h1>',
css: 'body { font-family: Inter; }', // optional extra CSS
},
options: {
format: 'A4', // A4 | A3 | Letter | Legal
margin: { top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' },
landscape: false,
print_background: true,
},
async: false,
})
// render.status === 'completed'
// render.output.url — signed download URL (1-hour TTL)
// render.output.pages
// render.output.bytes
// render.render_duration_msMarkdown → PDF
Markdown is automatically converted to styled HTML before rendering. No extra setup needed.
const render = await client.createRender({
source: {
type: 'markdown',
markdown: '# Invoice #42\n\n| Item | Price |\n|---|---|\n| Widget | $10 |',
css: 'body { max-width: 800px; }', // optional CSS override
},
async: false,
})URL → PDF
const render = await client.createRender({
source: { type: 'url', url: 'https://example.com' },
options: {
wait_until: 'networkidle0',
wait_for_selector: '#report-ready', // wait for this element before printing
headers: { Authorization: 'Bearer token' }, // extra HTTP headers
},
async: false,
})Async rendering with webhook
const render = await client.createRender({
source: { type: 'html', html: '<h1>Hello</h1>' },
async: true,
webhook_url: 'https://your-app.com/webhooks/pdf',
})
// render.status === 'queued'
// Your webhook receives a POST when the PDF is readyIdempotent requests
Pass an idempotency key as the second argument to safely retry without generating duplicate PDFs:
const render = await client.createRender(
{ source: { type: 'html', html: '<h1>Invoice #42</h1>' }, async: false },
'inv_42', // idempotency key
)Bypass cache
const render = await client.createRender({
source: { type: 'html', html: '<h1>Always fresh</h1>' },
force_render: true,
})Get a render
const render = await client.getRender('rnd_abc123')
console.log(render.output.url) // fresh signed URL every timeList renders
const list = await client.listRenders({ limit: 20, status: 'completed' })
for (const item of list.data) {
console.log(item.id, item.status, item.source_type, item.render_duration_ms)
}
// Paginate
if (list.has_more) {
const next = await client.listRenders({ before: list.next_before })
}Custom fonts
Upload fonts once. They are automatically injected as @font-face CSS into every render. Reference them by family name in your HTML/CSS.
import { readFileSync } from 'fs'
// Upload
const font = await client.uploadFont(
'Inter',
new Blob([readFileSync('./Inter-Regular.woff2')], { type: 'font/woff2' }),
'Inter-Regular.woff2',
)
// Use in HTML
await client.createRender({
source: { type: 'html', html: '<p style="font-family: Inter">Hello</p>' },
async: false,
})
// List uploaded fonts
const fonts = await client.listFonts()
// Remove a font
await client.deleteFont(font.id)API key management
// Create a key (raw key value shown once — store it securely)
const { key, ...meta } = await client.createKey('Production', 'live')
// List keys
const keys = await client.listKeys()
// Revoke a key
await client.deleteKey('key_abc123')Webhook endpoint management
// Register an endpoint (secret shown once — store it to verify payloads)
const endpoint = await client.createWebhook(
'https://your-app.com/webhooks/pdf',
['render.completed', 'render.failed'],
)
console.log(endpoint.secret) // whsec_...
// List endpoints
const webhooks = await client.listWebhooks()
// Delete an endpoint
await client.deleteWebhook('whk_abc123')Webhook payload verification
import { createHmac } from 'crypto'
function verifyWebhook(rawBody: string, signature: string, secret: string): boolean {
const expected = createHmac('sha256', secret).update(rawBody).digest('hex')
return signature === expected
}
// In your handler:
const valid = verifyWebhook(
JSON.stringify(req.body),
req.headers['x-pdfbrew-signature'],
process.env.WHSEC!,
)Stats & settings
// Usage stats
const stats = await client.getStats()
console.log(stats.total, stats.today, stats.this_month)
console.log(stats.daily) // 30-day chart
// Account settings
const settings = await client.getSettings()
console.log(settings.plan, settings.retention_days)
// Update PDF retention (1–30 days)
await client.updateSettings(14)Billing
// Current plan and subscription status
const billing = await client.getBillingStatus()
// { plan: 'pro', subscription_status: 'active', dodo_subscription_id: 'sub_...' }
// Create a checkout session (redirect user to checkout_url)
const { checkout_url } = await client.createCheckout('pro')
// Switch between paid plans (no checkout needed)
await client.changePlan('business')
// Cancel and downgrade to free
await client.cancelSubscription()Error handling
import { PdfBrew, PdfBrewError } from '@pdfbrew/sdk'
try {
const render = await client.createRender({ ... })
} catch (e) {
if (e instanceof PdfBrewError) {
console.log(e.code) // 'rate_limited' | 'quota_exceeded' | 'concurrent_limit' | ...
console.log(e.status) // HTTP status code
console.log(e.message)
}
}Error codes: invalid_source, invalid_options, invalid_request, render_timeout, render_failed, rate_limited, quota_exceeded, concurrent_limit, unauthorized, not_found, idempotency_conflict, internal_error.
Plan limits
| Plan | Monthly renders | Concurrent renders | |---|---|---| | Free | 100 | 2 | | Pro | 5,000 | 5 | | Business | 50,000 | 10 |
Exceeding the monthly quota returns a quota_exceeded error (429). Exceeding the concurrent cap returns concurrent_limit (429).
Runtime support
Works in Node.js, Deno, Bun, Cloudflare Workers, and the browser — uses the standard fetch API and FormData.
Links
- Interactive API docs
- OpenAPI spec
- Dashboard
- Base URL:
https://pdfbrew-api.avirajkhare00.workers.dev
License
MIT
