---
canonical: 'https://htmlcsstoimage.com/examples/account-statement'
title: 'Account Statement | HTML/CSS to Image API example'
description: 'Create a multi-page business account statement from JSON data using vanilla JavaScript, reusable HTML templates, and print-ready CSS.'
---

# Account Statement

Create a multi-page business account statement from JSON data using vanilla JavaScript, reusable HTML templates, and print-ready CSS.

This example creates a multi-page business account statement from a JSON response. It fetches a generated 60-day ledger from the [sample account statement endpoint](https://example-data.htmlcsstoimage.com/account-statement), then uses vanilla JavaScript and an HTML `<template>` to populate the account summary, balances, and transaction rows before rendering the PDF.

Keeping the sample data outside the HTML makes the document easier to understand and closer to a real integration. You can replace the example endpoint with your own API while keeping most of the statement layout and rendering request unchanged.

The finished statement includes account details, the statement period, opening and closing balances, credit and debit totals, categorized transactions, and account notices. Print-specific styles keep the transaction table readable as it continues across multiple pages.

This is a working HTML/CSS to Image API example that produces a PDF.
The rendered output, source, and non-content request parameters below show the parts needed to reproduce or adapt it.

## Rendered PDF and interactive editor

- [Open the rendered PDF](<https://hcti.io/v1/image/01a05959-8ba4-735c-b3d7-2ec4cde40592.pdf>)
- [Open the interactive example](https://htmlcsstoimage.com/examples/account-statement)

## HTML

```html
<p id="loading" class="loading">Loading account statement…</p>

<main id="statement" class="statement" hidden>
  <header class="statement-header">
    <div class="brand">
      <div class="brand-mark">M</div>
      <div>
        <strong data-institution-name></strong>
        <span>Business banking</span>
      </div>
    </div>

    <div class="statement-heading">
      <span>Account statement</span>
      <strong data-statement-period></strong>
      <small data-statement-id></small>
    </div>
  </header>

  <section class="account-details">
    <div>
      <span>Prepared for</span>
      <strong data-account-name></strong>
    </div>
    <div>
      <span>Account</span>
      <strong data-account-type></strong>
    </div>
    <div>
      <span>Account number</span>
      <strong data-account-number></strong>
    </div>
  </section>

  <section class="summary">
    <article class="summary-primary">
      <span>Closing balance</span>
      <strong data-closing-balance></strong>
    </article>
    <article>
      <span>Opening balance</span>
      <strong data-opening-balance></strong>
    </article>
    <article>
      <span>Credits</span>
      <strong class="credit" data-total-credits></strong>
    </article>
    <article>
      <span>Debits</span>
      <strong data-total-debits></strong>
    </article>
  </section>

  <section class="activity">
    <div class="section-heading">
      <div>
        <span>Account activity</span>
        <h1>Transactions</h1>
      </div>
      <p data-transaction-count></p>
    </div>

    <table>
      <thead>
        <tr>
          <th>Date</th>
          <th>Description</th>
          <th class="amount">Amount</th>
        </tr>
      </thead>
      <tbody id="transactions"></tbody>
    </table>
  </section>

  <footer class="statement-footer">
    <div>
      <strong>Questions about this statement?</strong>
      <span data-support></span>
    </div>
    <ul id="notices"></ul>
  </footer>
</main>

<template id="transaction-template">
  <tr>
    <td data-date></td>
    <td class="transaction">
      <strong data-description></strong>
      <span data-category></span>
    </td>
    <td class="amount" data-amount></td>
  </tr>
</template>

<template id="notice-template">
  <li data-notice></li>
</template>

<script>
  const endpoint =
    "https://example-data.htmlcsstoimage.com/account-statement";

  async function renderStatement() {
    const response = await fetch(endpoint);
    const data = await response.json();

    const money = cents =>
      new Intl.NumberFormat(data.account.locale, {
        style: "currency",
        currency: data.account.currency
      }).format(cents / 100);

    const date = value =>
      new Intl.DateTimeFormat(data.account.locale, {
        month: "short",
        day: "numeric",
        year: "numeric",
        timeZone: "UTC"
      }).format(new Date(`${value}T00:00:00Z`));

    document.querySelector("[data-institution-name]").textContent =
      data.institution.name;
    document.querySelector("[data-statement-period]").textContent =
      `${date(data.statement.period_start)}–${date(data.statement.period_end)}`;
    document.querySelector("[data-statement-id]").textContent =
      data.statement.statement_id;

    document.querySelector("[data-account-name]").textContent =
      data.account.name;
    document.querySelector("[data-account-type]").textContent =
      data.account.account_type;
    document.querySelector("[data-account-number]").textContent =
      data.account.account_number_masked;

    document.querySelector("[data-closing-balance]").textContent =
      money(data.summary.closing_balance_cents);
    document.querySelector("[data-opening-balance]").textContent =
      money(data.summary.opening_balance_cents);
    document.querySelector("[data-total-credits]").textContent =
      `+${money(data.summary.total_credits_cents)}`;
    document.querySelector("[data-total-debits]").textContent =
      money(data.summary.total_debits_cents);
    document.querySelector("[data-transaction-count]").textContent =
      `${data.meta.transaction_count} transactions`;

    document.querySelector("[data-support]").textContent =
      `${data.institution.support_email} · ${data.institution.support_phone}`;

    const transactionTemplate =
      document.querySelector("#transaction-template");
    const transactions = document.createDocumentFragment();

    for (const transaction of data.transactions) {
      const row =
        transactionTemplate.content.firstElementChild.cloneNode(true);

      row.querySelector("[data-date]").textContent =
        date(transaction.posted_on);
      row.querySelector("[data-description]").textContent =
        transaction.description;
      row.querySelector("[data-category]").textContent =
        transaction.category;

      const amount = row.querySelector("[data-amount]");
      amount.textContent = money(transaction.amount_cents);
      amount.classList.add(transaction.direction);

      transactions.append(row);
    }

    document.querySelector("#transactions").append(transactions);

    const noticeTemplate = document.querySelector("#notice-template");
    const notices = document.createDocumentFragment();

    for (const message of data.notices) {
      const notice =
        noticeTemplate.content.firstElementChild.cloneNode(true);

      notice.textContent = message;
      notices.append(notice);
    }

    document.querySelector("#notices").append(notices);
    document.querySelector("#loading").remove();
    document.querySelector("#statement").hidden = false;

    requestAnimationFrame(() => ScreenshotReady());
  }

  renderStatement();
</script>
```

## CSS

```css
* {
  box-sizing: border-box;
}

html {
  color: #172033;
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, sans-serif;
  font-size: 14px;
}

body {
  margin: 0;
  padding: 32px;
  background: #eef2f6;
}

.statement {
  width: 100%;
  max-width: 7.5in;
  margin: 0 auto;
  padding: 36px;
  background: white;
  border-radius: 12px;
  box-shadow: 0 12px 36px rgba(23, 32, 51, 0.12);
}

.loading {
  margin: 0;
  color: #64748b;
}

.statement-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding-bottom: 24px;
  border-bottom: 2px solid #172033;
}

.brand {
  display: flex;
  align-items: center;
  gap: 12px;
}

.brand-mark {
  display: grid;
  width: 40px;
  height: 40px;
  place-items: center;
  color: white;
  background: #3157d5;
  border-radius: 10px;
  font-size: 20px;
  font-weight: 800;
}

.brand strong,
.brand span,
.statement-heading span,
.statement-heading strong,
.statement-heading small {
  display: block;
}

.brand strong {
  font-size: 15px;
}

.brand span,
.statement-heading span,
.statement-heading small {
  margin-top: 3px;
  color: #64748b;
  font-size: 11px;
}

.statement-heading {
  text-align: right;
}

.statement-heading span {
  margin: 0 0 5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.statement-heading strong {
  font-size: 15px;
}

.account-details {
  display: grid;
  grid-template-columns: 1.5fr 1fr 1fr;
  gap: 24px;
  padding: 22px 0;
}

.account-details span,
.summary span {
  display: block;
  margin-bottom: 6px;
  color: #64748b;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.account-details strong {
  font-size: 13px;
}

.summary {
  display: grid;
  grid-template-columns: 1.4fr repeat(3, 1fr);
  overflow: hidden;
  margin-bottom: 32px;
  border: 1px solid #dbe2ea;
  border-radius: 10px;
}

.summary article {
  min-height: 82px;
  padding: 17px;
  border-left: 1px solid #dbe2ea;
}

.summary article:first-child {
  border-left: 0;
}

.summary strong {
  font-size: 15px;
}

.summary-primary {
  color: white;
  background: #172033;
}

.summary-primary span {
  color: #b8c3d4;
}

.summary-primary strong {
  font-size: 21px;
}

.credit {
  color: #16805b;
}

.section-heading {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  margin-bottom: 13px;
}

.section-heading span {
  color: #3157d5;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.section-heading h1 {
  margin: 3px 0 0;
  font-size: 20px;
}

.section-heading p {
  margin: 0;
  color: #64748b;
  font-size: 11px;
}

table {
  width: 100%;
  border-collapse: collapse;
}

thead {
  display: table-header-group;
}

th {
  padding: 10px 8px;
  color: #64748b;
  background: #f5f7fa;
  border-bottom: 1px solid #dbe2ea;
  font-size: 10px;
  letter-spacing: 0.07em;
  text-align: left;
  text-transform: uppercase;
}

td {
  padding: 10px 8px;
  border-bottom: 1px solid #e7ebf0;
  font-size: 11px;
  vertical-align: top;
}

tr {
  break-inside: avoid;
}

th:first-child,
td:first-child {
  width: 106px;
}

.amount {
  width: 105px;
  text-align: right;
  white-space: nowrap;
}

.transaction strong,
.transaction span {
  display: block;
}

.transaction span {
  margin-top: 3px;
  color: #718096;
  font-size: 9px;
}

td.credit {
  color: #16805b;
  font-weight: 700;
}

.statement-footer {
  display: grid;
  grid-template-columns: 1fr 1.5fr;
  gap: 30px;
  margin-top: 28px;
  padding-top: 20px;
  border-top: 2px solid #172033;
  break-inside: avoid;
}

.statement-footer strong,
.statement-footer span {
  display: block;
}

.statement-footer span,
.statement-footer li {
  color: #64748b;
  font-size: 9px;
  line-height: 1.55;
}

.statement-footer ul {
  margin: 0;
  padding-left: 16px;
}

@media print {
  body {
    padding: 0;
    background: white;
  }

  .statement {
    max-width: none;
    padding: 0;
    border-radius: 0;
    box-shadow: none;
  }
}
```

## Request parameters

These are the additional non-content parameters used by the example. Combine them with the HTML and CSS above when constructing an API request.

```json
{
  "media_type": "print",
  "pdf_options": {
    "page_height": "11in",
    "page_width": "8.5in",
    "scale": 1,
    "margins": [
      "0.4in",
      "0.4in",
      "0.4in",
      "0.4in"
    ],
    "print_background": true
  },
  "render_when_ready": true
}
```

## Complete TypeScript request (HCTI client)

This is the complete TypeScript request for this example using HCTI client. It includes the HTML, CSS, authentication setup, API options, and response handling needed to reproduce the output.

```typescript
import { HtmlCssToImageClient, CreateHtmlCssImageRequest, PDFOptions } from "@html-css-to-image/client";

const client = HtmlCssToImageClient.fromEnv();

const request = new CreateHtmlCssImageRequest({
    html: `<p id="loading" class="loading">Loading account statement…</p>

<main id="statement" class="statement" hidden>
  <header class="statement-header">
    <div class="brand">
      <div class="brand-mark">M</div>
      <div>
        <strong data-institution-name></strong>
        <span>Business banking</span>
      </div>
    </div>

    <div class="statement-heading">
      <span>Account statement</span>
      <strong data-statement-period></strong>
      <small data-statement-id></small>
    </div>
  </header>

  <section class="account-details">
    <div>
      <span>Prepared for</span>
      <strong data-account-name></strong>
    </div>
    <div>
      <span>Account</span>
      <strong data-account-type></strong>
    </div>
    <div>
      <span>Account number</span>
      <strong data-account-number></strong>
    </div>
  </section>

  <section class="summary">
    <article class="summary-primary">
      <span>Closing balance</span>
      <strong data-closing-balance></strong>
    </article>
    <article>
      <span>Opening balance</span>
      <strong data-opening-balance></strong>
    </article>
    <article>
      <span>Credits</span>
      <strong class="credit" data-total-credits></strong>
    </article>
    <article>
      <span>Debits</span>
      <strong data-total-debits></strong>
    </article>
  </section>

  <section class="activity">
    <div class="section-heading">
      <div>
        <span>Account activity</span>
        <h1>Transactions</h1>
      </div>
      <p data-transaction-count></p>
    </div>

    <table>
      <thead>
        <tr>
          <th>Date</th>
          <th>Description</th>
          <th class="amount">Amount</th>
        </tr>
      </thead>
      <tbody id="transactions"></tbody>
    </table>
  </section>

  <footer class="statement-footer">
    <div>
      <strong>Questions about this statement?</strong>
      <span data-support></span>
    </div>
    <ul id="notices"></ul>
  </footer>
</main>

<template id="transaction-template">
  <tr>
    <td data-date></td>
    <td class="transaction">
      <strong data-description></strong>
      <span data-category></span>
    </td>
    <td class="amount" data-amount></td>
  </tr>
</template>

<template id="notice-template">
  <li data-notice></li>
</template>

<script>
  const endpoint =
    "https://example-data.htmlcsstoimage.com/account-statement";

  async function renderStatement() {
    const response = await fetch(endpoint);
    const data = await response.json();

    const money = cents =>
      new Intl.NumberFormat(data.account.locale, {
        style: "currency",
        currency: data.account.currency
      }).format(cents / 100);

    const date = value =>
      new Intl.DateTimeFormat(data.account.locale, {
        month: "short",
        day: "numeric",
        year: "numeric",
        timeZone: "UTC"
      }).format(new Date(\`\${value}T00:00:00Z\`));

    document.querySelector("[data-institution-name]").textContent =
      data.institution.name;
    document.querySelector("[data-statement-period]").textContent =
      \`\${date(data.statement.period_start)}–\${date(data.statement.period_end)}\`;
    document.querySelector("[data-statement-id]").textContent =
      data.statement.statement_id;

    document.querySelector("[data-account-name]").textContent =
      data.account.name;
    document.querySelector("[data-account-type]").textContent =
      data.account.account_type;
    document.querySelector("[data-account-number]").textContent =
      data.account.account_number_masked;

    document.querySelector("[data-closing-balance]").textContent =
      money(data.summary.closing_balance_cents);
    document.querySelector("[data-opening-balance]").textContent =
      money(data.summary.opening_balance_cents);
    document.querySelector("[data-total-credits]").textContent =
      \`+\${money(data.summary.total_credits_cents)}\`;
    document.querySelector("[data-total-debits]").textContent =
      money(data.summary.total_debits_cents);
    document.querySelector("[data-transaction-count]").textContent =
      \`\${data.meta.transaction_count} transactions\`;

    document.querySelector("[data-support]").textContent =
      \`\${data.institution.support_email} · \${data.institution.support_phone}\`;

    const transactionTemplate =
      document.querySelector("#transaction-template");
    const transactions = document.createDocumentFragment();

    for (const transaction of data.transactions) {
      const row =
        transactionTemplate.content.firstElementChild.cloneNode(true);

      row.querySelector("[data-date]").textContent =
        date(transaction.posted_on);
      row.querySelector("[data-description]").textContent =
        transaction.description;
      row.querySelector("[data-category]").textContent =
        transaction.category;

      const amount = row.querySelector("[data-amount]");
      amount.textContent = money(transaction.amount_cents);
      amount.classList.add(transaction.direction);

      transactions.append(row);
    }

    document.querySelector("#transactions").append(transactions);

    const noticeTemplate = document.querySelector("#notice-template");
    const notices = document.createDocumentFragment();

    for (const message of data.notices) {
      const notice =
        noticeTemplate.content.firstElementChild.cloneNode(true);

      notice.textContent = message;
      notices.append(notice);
    }

    document.querySelector("#notices").append(notices);
    document.querySelector("#loading").remove();
    document.querySelector("#statement").hidden = false;

    requestAnimationFrame(() => ScreenshotReady());
  }

  renderStatement();
</script>`,
    css: `* {
  box-sizing: border-box;
}

html {
  color: #172033;
  font-family: Inter, ui-sans-serif, system-ui, -apple-system, sans-serif;
  font-size: 14px;
}

body {
  margin: 0;
  padding: 32px;
  background: #eef2f6;
}

.statement {
  width: 100%;
  max-width: 7.5in;
  margin: 0 auto;
  padding: 36px;
  background: white;
  border-radius: 12px;
  box-shadow: 0 12px 36px rgba(23, 32, 51, 0.12);
}

.loading {
  margin: 0;
  color: #64748b;
}

.statement-header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  padding-bottom: 24px;
  border-bottom: 2px solid #172033;
}

.brand {
  display: flex;
  align-items: center;
  gap: 12px;
}

.brand-mark {
  display: grid;
  width: 40px;
  height: 40px;
  place-items: center;
  color: white;
  background: #3157d5;
  border-radius: 10px;
  font-size: 20px;
  font-weight: 800;
}

.brand strong,
.brand span,
.statement-heading span,
.statement-heading strong,
.statement-heading small {
  display: block;
}

.brand strong {
  font-size: 15px;
}

.brand span,
.statement-heading span,
.statement-heading small {
  margin-top: 3px;
  color: #64748b;
  font-size: 11px;
}

.statement-heading {
  text-align: right;
}

.statement-heading span {
  margin: 0 0 5px;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}

.statement-heading strong {
  font-size: 15px;
}

.account-details {
  display: grid;
  grid-template-columns: 1.5fr 1fr 1fr;
  gap: 24px;
  padding: 22px 0;
}

.account-details span,
.summary span {
  display: block;
  margin-bottom: 6px;
  color: #64748b;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.account-details strong {
  font-size: 13px;
}

.summary {
  display: grid;
  grid-template-columns: 1.4fr repeat(3, 1fr);
  overflow: hidden;
  margin-bottom: 32px;
  border: 1px solid #dbe2ea;
  border-radius: 10px;
}

.summary article {
  min-height: 82px;
  padding: 17px;
  border-left: 1px solid #dbe2ea;
}

.summary article:first-child {
  border-left: 0;
}

.summary strong {
  font-size: 15px;
}

.summary-primary {
  color: white;
  background: #172033;
}

.summary-primary span {
  color: #b8c3d4;
}

.summary-primary strong {
  font-size: 21px;
}

.credit {
  color: #16805b;
}

.section-heading {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  margin-bottom: 13px;
}

.section-heading span {
  color: #3157d5;
  font-size: 10px;
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.section-heading h1 {
  margin: 3px 0 0;
  font-size: 20px;
}

.section-heading p {
  margin: 0;
  color: #64748b;
  font-size: 11px;
}

table {
  width: 100%;
  border-collapse: collapse;
}

thead {
  display: table-header-group;
}

th {
  padding: 10px 8px;
  color: #64748b;
  background: #f5f7fa;
  border-bottom: 1px solid #dbe2ea;
  font-size: 10px;
  letter-spacing: 0.07em;
  text-align: left;
  text-transform: uppercase;
}

td {
  padding: 10px 8px;
  border-bottom: 1px solid #e7ebf0;
  font-size: 11px;
  vertical-align: top;
}

tr {
  break-inside: avoid;
}

th:first-child,
td:first-child {
  width: 106px;
}

.amount {
  width: 105px;
  text-align: right;
  white-space: nowrap;
}

.transaction strong,
.transaction span {
  display: block;
}

.transaction span {
  margin-top: 3px;
  color: #718096;
  font-size: 9px;
}

td.credit {
  color: #16805b;
  font-weight: 700;
}

.statement-footer {
  display: grid;
  grid-template-columns: 1fr 1.5fr;
  gap: 30px;
  margin-top: 28px;
  padding-top: 20px;
  border-top: 2px solid #172033;
  break-inside: avoid;
}

.statement-footer strong,
.statement-footer span {
  display: block;
}

.statement-footer span,
.statement-footer li {
  color: #64748b;
  font-size: 9px;
  line-height: 1.55;
}

.statement-footer ul {
  margin: 0;
  padding-left: 16px;
}

@media print {
  body {
    padding: 0;
    background: white;
  }

  .statement {
    max-width: none;
    padding: 0;
    border-radius: 0;
    box-shadow: none;
  }
}`,
    render_when_ready: true,
    pdf_options: new PDFOptions({
        page_height: {
            value: 11,
            unit: "in",
        },
        page_width: {
            value: 8.5,
            unit: "in",
        },
        scale: 1,
        margins: {
            top: {
                value: 0.4,
                unit: "in",
            },
            right: {
                value: 0.4,
                unit: "in",
            },
            bottom: {
                value: 0.4,
                unit: "in",
            },
            left: {
                value: 0.4,
                unit: "in",
            },
        },
        print_background: true,
    }),
    media_type: "print",
});

const result = await client.createImage(request);
if (result.success) {
    console.log(result.url);
} else {
    console.error(result.error);
}

```

## Other languages and request formats

Open one of these Markdown pages to view the same example as a complete request in another language or client format.

- **TypeScript**
  - [HCTI client](https://htmlcsstoimage.com/examples/account-statement/typescript-client.md) — shown above
  - [fetch](https://htmlcsstoimage.com/examples/account-statement/typescript.md)
- **C#**
  - [HCTI client](https://htmlcsstoimage.com/examples/account-statement/csharp-client.md)
  - [HttpClient](https://htmlcsstoimage.com/examples/account-statement/csharp.md)
- **Python**
  - [requests](https://htmlcsstoimage.com/examples/account-statement/python.md)
- **PHP**
  - [cURL](https://htmlcsstoimage.com/examples/account-statement/php.md)
- **Ruby**
  - [HCTI client](https://htmlcsstoimage.com/examples/account-statement/ruby-client.md)
  - [Net::HTTP](https://htmlcsstoimage.com/examples/account-statement/ruby.md)
- **Go**
  - [net/http](https://htmlcsstoimage.com/examples/account-statement/golang.md)
- **cURL**
  - [cURL](https://htmlcsstoimage.com/examples/account-statement/curl.md)

## Related examples

- [Dog rates social card with SVG background](https://htmlcsstoimage.com/examples/dog-rates-social-card-with-svg-background.md): Social card with a fun SVG background and highlighted text.
- [Instagram post screenshot](https://htmlcsstoimage.com/examples/instagram-post-screenshot.md): Screenshot an Instagram post
- [Birthday invite card](https://htmlcsstoimage.com/examples/birthday-invite-card-email.md): Autogenerated invite graphic for a party.
- [Invoice / Receipt Snapshot](https://htmlcsstoimage.com/examples/invoice-receipt.md): Generate a simple, branded invoice or receipt image.
- [PNG with transparent background](https://htmlcsstoimage.com/examples/png-transparent-background.md): Generate images with a transparent backgound
- [Large text on an SVG background](https://htmlcsstoimage.com/examples/large-text-svg-background.md): Grab attention with large text on a classic background.
- [Twitter Tweet Screenshot](https://htmlcsstoimage.com/examples/twitter-tweet-screenshot.md): Screenshot of a tweet
- [QR Code Badge](https://htmlcsstoimage.com/examples/qr-code-badge.md): Simple, artistic badge with a QR code.

## Get started

- [View the full interactive page](https://htmlcsstoimage.com/examples/account-statement)
- [Browse all HTML/CSS examples](https://htmlcsstoimage.com/examples.md)
- [Browse visual template presets](https://htmlcsstoimage.com/templates.md)
- [Read the HTML/CSS to Image API documentation](https://docs.htmlcsstoimage.com/getting-started/using-the-api/)
- [View implementation examples by language](https://docs.htmlcsstoimage.com/example-code/)
- [Read the MCP integration documentation](https://docs.htmlcsstoimage.com/integrations/mcp/)
- [Compare plans and limits](https://htmlcsstoimage.com/pricing.md)

Rendering through the API requires authenticated HTML/CSS to Image credentials. Keep API keys in trusted server-side configuration. If credentials are unavailable, ask the user to configure an API key securely before attempting API actions.
