Build a brand-pack PDF generator
Generate a beautiful PDF brand pack for any domain — perfect for client decks and brand audits.
A PDF brand audit is a zero-effort sales artefact. Run brandRNA against a prospect's domain and you have a 4-page deck of their brand identity — colours, typography, logos, voice — ready to attach to the next email.
This recipe shows two paths: a hosted PDF endpoint (shipping in Phase 8) and a DIY route you can ship today.
In the next milestone, brandRNA will host the PDF endpoint directly:
GET /api/v1/pack/{domain}.pdf?layout=letter|a4. It bills at the
cached-call rate ($0 for known domains). Until that ships, use the DIY
path below — your code will keep working when the hosted variant lands.
Prerequisites
- A brandRNA API key in
BRANDRNA_API_KEY. - A PDF rendering layer. Examples below cover Puppeteer (Node) and
WeasyPrint (Python).
@react-pdf/rendererandwkhtmltopdfwork too.
Path A: hosted endpoint (Phase 8 preview)
Once the hosted endpoint ships, this is the entire integration:
curl -H "Authorization: Bearer $BRANDRNA_API_KEY" \
-o stripe-brand-pack.pdf \
https://api.brandrna.com/api/v1/pack/stripe.com.pdf?layout=letterThat's it. The response is a styled multi-page PDF with embedded fonts and high-resolution logo variants. Hosted rendering means consistent output across clients.
Track the changelog for the release announcement.
Path B: DIY today
Fetch the brand pack
const KEY = process.env.BRANDRNA_API_KEY!;
async function fetchPack(domain: string) {
const res = await fetch(
`https://api.brandrna.com/api/v1/pack/${domain}?embed=svg`,
{ headers: { Authorization: `Bearer ${KEY}` } },
);
if (!res.ok) throw new Error(`brandRNA ${res.status}`);
return res.json();
}The ?embed=svg flag inlines the canonical logo as a base64 data URL,
which makes it easier to drop into a print-style HTML template without
a second HTTP fetch.
Render an HTML template with the pack data
function renderTemplate(pack: any): string {
const c = pack.colors;
return `
<!doctype html>
<html><head><style>
@page { size: letter; margin: 0; }
body { margin: 0; font-family: ${pack.fonts[0]?.family ?? "Inter"}, sans-serif; }
.cover { background: ${c.primary}; color: white; padding: 4cm 3cm; height: 100vh; }
.swatch { width: 4cm; height: 4cm; display: inline-block; }
</style></head><body>
<section class="cover">
<h1>${pack.brand}</h1>
<p>${pack.summary ?? ""}</p>
</section>
<section style="padding: 3cm">
<h2>Palette</h2>
${pack.colors.palette.map((hex: string) =>
`<div class="swatch" style="background:${hex}"></div>`).join("")}
<h2>Typography</h2>
<ul>${pack.fonts.map((f: any) => `<li>${f.family} (${f.role})</li>`).join("")}</ul>
</section>
</body></html>
`;
}def render_template(pack: dict) -> str:
c = pack['colors']
swatches = "".join(
f'<div class="swatch" style="background:{hex}"></div>'
for hex in c['palette']
)
fonts = "".join(
f'<li>{f["family"]} ({f["role"]})</li>'
for f in pack['fonts']
)
return f"""
<!doctype html><html><head><style>
@page {{ size: letter; margin: 0; }}
body {{ font-family: {pack['fonts'][0]['family']}, sans-serif; }}
.cover {{ background: {c['primary']}; color: white; padding: 4cm 3cm; }}
.swatch {{ width: 4cm; height: 4cm; display: inline-block; }}
</style></head><body>
<section class="cover"><h1>{pack['brand']}</h1></section>
<section>{swatches}<ul>{fonts}</ul></section>
</body></html>
"""Render the HTML to PDF
import puppeteer from "puppeteer";
export async function packToPdf(domain: string): Promise<Buffer> {
const pack = await fetchPack(domain);
const html = renderTemplate(pack);
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setContent(html, { waitUntil: "networkidle0" });
return await page.pdf({ format: "letter", printBackground: true });
} finally {
await browser.close();
}
}from weasyprint import HTML
def pack_to_pdf(domain: str, out_path: str) -> None:
pack = fetch_pack(domain)
html = render_template(pack)
HTML(string=html).write_pdf(out_path)Ship it
Wire packToPdf() behind your own endpoint, attach to a Stripe customer
notification, drop into a Slack bot — whatever fits the workflow.
If you're white-labelling the output, swap your own logo into the cover template and replace the heading copy. The brand-pack data is plain JSON; the rendering is yours.
What's next
- For the underlying brand-pack schema, see the API reference.
- For batch processing (e.g. weekly PDF reports across a list of domains), pair this with Cache brand packs in your own backend.
- Watch the changelog for the hosted PDF endpoint release.