Recipes

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/renderer and wkhtmltopdf work 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=letter

That'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

On this page