API

Template to PDF

Generate a PDF from a template with dynamic data. Use predefined templates from the cloudlayer.io template gallery or provide your own custom HTML template. Templates support Handlebars syntax for data binding.

Endpoint

POST /v1/template/pdf

All requests are processed synchronously. The response body is the raw PDF file.

POST /v2/template/pdf

Requests default to asynchronous processing (async: true, storage: true). Add "async": false to receive the raw PDF directly. See Sync vs. Async for details.

Supports two content types:

Content TypeUse Case
application/jsonInline templates (base64-encoded) or predefined template IDs with JSON data.
multipart/form-dataUpload template files directly alongside JSON data.

Quick Start: Predefined Template

Use a template from the cloudlayer.io gallery by referencing its templateId:

cURL

curl -X POST "https://api.cloudlayer.io/v1/template/pdf" \
  -H "X-API-Key: your-api-key-here" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "professional-invoice",
    "data": {
      "invoiceNumber": "INV-2024-001",
      "companyName": "Acme Corp",
      "customerName": "Jane Smith",
      "items": [
        {"name": "Widget A", "quantity": 10, "price": 25.00},
        {"name": "Widget B", "quantity": 5, "price": 40.00}
      ],
      "total": 450.00
    }
  }' \
  --output invoice.pdf

JavaScript (fetch)

const response = await fetch("https://api.cloudlayer.io/v1/template/pdf", {
  method: "POST",
  headers: {
    "X-API-Key": "your-api-key-here",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    templateId: "professional-invoice",
    data: {
      invoiceNumber: "INV-2024-001",
      companyName: "Acme Corp",
      customerName: "Jane Smith",
      items: [
        { name: "Widget A", quantity: 10, price: 25.0 },
        { name: "Widget B", quantity: 5, price: 40.0 },
      ],
      total: 450.0,
    },
  }),
});

const pdf = await response.arrayBuffer();

Python (requests)

import requests

response = requests.post(
    "https://api.cloudlayer.io/v1/template/pdf",
    headers={
        "X-API-Key": "your-api-key-here",
        "Content-Type": "application/json",
    },
    json={
        "templateId": "professional-invoice",
        "data": {
            "invoiceNumber": "INV-2024-001",
            "companyName": "Acme Corp",
            "customerName": "Jane Smith",
            "items": [
                {"name": "Widget A", "quantity": 10, "price": 25.00},
                {"name": "Widget B", "quantity": 5, "price": 40.00},
            ],
            "total": 450.00,
        },
    },
)

with open("invoice.pdf", "wb") as f:
    f.write(response.content)

cURL

curl -X POST "https://api.cloudlayer.io/v2/template/pdf" \
  -H "X-API-Key: your-api-key-here" \
  -H "Content-Type: application/json" \
  -d '{
    "templateId": "professional-invoice",
    "data": {
      "invoiceNumber": "INV-2024-001",
      "companyName": "Acme Corp",
      "customerName": "Jane Smith",
      "items": [
        {"name": "Widget A", "quantity": 10, "price": 25.00},
        {"name": "Widget B", "quantity": 5, "price": 40.00}
      ],
      "total": 450.00
    },
    "async": false
  }' \
  --output invoice.pdf

JavaScript (fetch)

const response = await fetch("https://api.cloudlayer.io/v2/template/pdf", {
  method: "POST",
  headers: {
    "X-API-Key": "your-api-key-here",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    templateId: "professional-invoice",
    data: {
      invoiceNumber: "INV-2024-001",
      companyName: "Acme Corp",
      customerName: "Jane Smith",
      items: [
        { name: "Widget A", quantity: 10, price: 25.0 },
        { name: "Widget B", quantity: 5, price: 40.0 },
      ],
      total: 450.0,
    },
    async: false,
  }),
});

const pdf = await response.arrayBuffer();

Python (requests)

import requests

response = requests.post(
    "https://api.cloudlayer.io/v2/template/pdf",
    headers={
        "X-API-Key": "your-api-key-here",
        "Content-Type": "application/json",
    },
    json={
        "templateId": "professional-invoice",
        "data": {
            "invoiceNumber": "INV-2024-001",
            "companyName": "Acme Corp",
            "customerName": "Jane Smith",
            "items": [
                {"name": "Widget A", "quantity": 10, "price": 25.00},
                {"name": "Widget B", "quantity": 5, "price": 40.00},
            ],
            "total": 450.00,
        },
        "async": False,
    },
)

with open("invoice.pdf", "wb") as f:
    f.write(response.content)

Tip: Omit "async": false to use the default async mode. The API will return a JSON response with the job ID immediately, and the generated PDF will be stored and accessible via the Assets endpoint.


Quick Start: Custom Template

Provide your own HTML template as a base64-encoded string:

cURL

TEMPLATE=$(cat <<'HTML'
<!DOCTYPE html>
<html>
<head>
  <style>
    body { font-family: Arial, sans-serif; padding: 40px; }
    h1 { color: #2563eb; }
    .detail { margin: 8px 0; }
    .label { font-weight: bold; color: #666; }
  </style>
</head>
<body>
  <h1>{{title}}</h1>
  <div class="detail"><span class="label">Date:</span> {{date}}</div>
  <div class="detail"><span class="label">Prepared for:</span> {{recipient}}</div>
  <div style="margin-top: 20px;">{{{content}}}</div>
</body>
</html>
HTML
)

TEMPLATE_BASE64=$(echo "$TEMPLATE" | base64 -w 0)

curl -X POST "https://api.cloudlayer.io/v2/template/pdf" \
  -H "X-API-Key: your-api-key-here" \
  -H "Content-Type: application/json" \
  -d "{
    \"template\": \"$TEMPLATE_BASE64\",
    \"data\": {
      \"title\": \"Monthly Report\",
      \"date\": \"January 2024\",
      \"recipient\": \"Jane Smith\",
      \"content\": \"<p>This is the report content with <strong>HTML</strong> support.</p>\"
    }
  }" \
  --output report.pdf

JavaScript (fetch)

const template = `<!DOCTYPE html>
<html>
<head>
  <style>
    body { font-family: Arial, sans-serif; padding: 40px; }
    h1 { color: #2563eb; }
    .detail { margin: 8px 0; }
    .label { font-weight: bold; color: #666; }
  </style>
</head>
<body>
  <h1>{{title}}</h1>
  <div class="detail"><span class="label">Date:</span> {{date}}</div>
  <div class="detail"><span class="label">Prepared for:</span> {{recipient}}</div>
  <div style="margin-top: 20px;">{{{content}}}</div>
</body>
</html>`;

const response = await fetch("https://api.cloudlayer.io/v2/template/pdf", {
  method: "POST",
  headers: {
    "X-API-Key": "your-api-key-here",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    template: btoa(template),
    data: {
      title: "Monthly Report",
      date: "January 2024",
      recipient: "Jane Smith",
      content:
        "<p>This is the report content with <strong>HTML</strong> support.</p>",
    },
  }),
});

const pdf = await response.arrayBuffer();

Python (requests)

import base64
import requests

template = """<!DOCTYPE html>
<html>
<head>
  <style>
    body { font-family: Arial, sans-serif; padding: 40px; }
    h1 { color: #2563eb; }
  </style>
</head>
<body>
  <h1>{{title}}</h1>
  <p>Prepared for: {{recipient}}</p>
  <div>{{{content}}}</div>
</body>
</html>"""

response = requests.post(
    "https://api.cloudlayer.io/v2/template/pdf",
    headers={
        "X-API-Key": "your-api-key-here",
        "Content-Type": "application/json",
    },
    json={
        "template": base64.b64encode(template.encode()).decode(),
        "data": {
            "title": "Monthly Report",
            "recipient": "Jane Smith",
            "content": "<p>Report content here.</p>",
        },
    },
)

with open("report.pdf", "wb") as f:
    f.write(response.content)

Parameters

Template Parameters

You must provide either templateId (for a predefined template) or template (for a custom template), but not both.

ParameterTypeRequiredDefaultDescription
templateIdstringConditionalThe ID of a predefined template from the cloudlayer.io template gallery. Browse available templates in the dashboard.
templatestringConditionalA custom HTML template as a base64-encoded string. Supports Handlebars syntax for data binding.
dataobjectNo{}Key-value pairs of data to inject into the template. Keys correspond to Handlebars placeholders in the template (e.g., {{name}} is populated by data.name).

Base Parameters

ParameterTypeDefaultDescription
autoScrollbooleanfalseScroll the page to trigger lazy-loaded content before capture.
delaynumber0Wait time in milliseconds after the page loads before generating.
filenamestringSet the Content-Disposition filename on the response.
generatePreviewobjectGenerate a thumbnail preview image. See HTML to PDF — generatePreview.
heightstringOverride page height with a CSS value.
widthstringOverride page width with a CSS value.
inlinebooleanfalseDisplay the PDF inline in the browser instead of downloading.
landscapebooleanfalseGenerate in landscape orientation.
preferCSSPageSizebooleanfalseUse CSS @page size instead of format.
projectIdstringAssociate with a project for dashboard organization.
scalenumber1Page rendering scale (0.1 to 2).
timeoutnumber30000Maximum page load time in milliseconds.
timeZonestringBrowser timezone (IANA name).
viewPortobjectBrowser viewport configuration. See HTML to PDF — viewPort.
waitForSelectorobjectWait for a CSS selector before generating. See HTML to PDF — waitForSelector.
waitUntilstring"networkidle2"Navigation completion strategy.

PDF Parameters

ParameterTypeDefaultDescription
pageRangesstringPage ranges to include (e.g., "1-5", "1,3,5").
formatstring"letter"Page size: "letter", "legal", "tabloid", "ledger", "a0" through "a6".
marginobjectPage margins. See HTML to PDF — margin.
printBackgroundbooleantrueInclude background colors and images.
headerTemplateobjectCustom page header. See HTML to PDF — headerTemplate.
footerTemplateobjectCustom page footer. See HTML to PDF — footerTemplate.

Multipart Uploads

For uploading template files directly (instead of base64-encoding), use multipart/form-data:

cURL

curl -X POST "https://api.cloudlayer.io/v2/template/pdf" \
  -H "X-API-Key: your-api-key-here" \
  -F "template=@./my-template.html" \
  -F 'data={"invoiceNumber":"INV-001","total":450.00}'

JavaScript (fetch)

const formData = new FormData();
formData.append("template", new Blob([templateHtml], { type: "text/html" }), "template.html");
formData.append("data", JSON.stringify({
  invoiceNumber: "INV-001",
  total: 450.00,
}));

const response = await fetch("https://api.cloudlayer.io/v2/template/pdf", {
  method: "POST",
  headers: {
    "X-API-Key": "your-api-key-here",
    // Do not set Content-Type — fetch sets it automatically with the boundary
  },
  body: formData,
});

const pdf = await response.arrayBuffer();

Python (requests)

import json
import requests

with open("my-template.html", "rb") as f:
    template_file = f.read()

response = requests.post(
    "https://api.cloudlayer.io/v2/template/pdf",
    headers={"X-API-Key": "your-api-key-here"},
    files={"template": ("template.html", template_file, "text/html")},
    data={"data": json.dumps({"invoiceNumber": "INV-001", "total": 450.00})},
)

with open("invoice.pdf", "wb") as f:
    f.write(response.content)

Template Syntax

Templates use Handlebars syntax for data binding. Here are the most common patterns:

Variable Output

<!-- Escaped output (HTML entities are escaped) -->
<p>Hello, {{name}}!</p>

<!-- Unescaped output (raw HTML is rendered) -->
<div>{{{htmlContent}}}</div>

Conditionals

{{#if isPaid}}
  <span class="badge paid">Paid</span>
{{else}}
  <span class="badge unpaid">Unpaid</span>
{{/if}}

Loops

<table>
  <thead>
    <tr><th>Item</th><th>Qty</th><th>Price</th></tr>
  </thead>
  <tbody>
    {{#each items}}
    <tr>
      <td>{{this.name}}</td>
      <td>{{this.quantity}}</td>
      <td>${{this.price}}</td>
    </tr>
    {{/each}}
  </tbody>
</table>

Nested Data

<div>
  <p>{{company.name}}</p>
  <p>{{company.address.street}}</p>
  <p>{{company.address.city}}, {{company.address.state}} {{company.address.zip}}</p>
</div>

Full Example: Invoice with Custom Template

JavaScript (fetch)

const template = `<!DOCTYPE html>
<html>
<head>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: 'Helvetica Neue', Arial, sans-serif; color: #333; padding: 60px; }
    .header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 40px; }
    .company-name { font-size: 28px; font-weight: 700; color: #1a1a1a; }
    .invoice-meta { text-align: right; }
    .invoice-number { font-size: 20px; font-weight: 600; color: #2563eb; }
    .customer { margin-bottom: 30px; padding: 20px; background: #f8f9fa; border-radius: 8px; }
    table { width: 100%; border-collapse: collapse; margin: 20px 0; }
    th { background: #2563eb; color: white; padding: 12px 16px; text-align: left; }
    td { padding: 12px 16px; border-bottom: 1px solid #eee; }
    tr:nth-child(even) { background: #f8f9fa; }
    .total-row { font-size: 18px; font-weight: 700; background: #f0f4ff !important; }
    .footer { margin-top: 40px; padding-top: 20px; border-top: 1px solid #eee; font-size: 12px; color: #999; }
  </style>
</head>
<body>
  <div class="header">
    <div class="company-name">{{companyName}}</div>
    <div class="invoice-meta">
      <div class="invoice-number">{{invoiceNumber}}</div>
      <div>Date: {{date}}</div>
      <div>Due: {{dueDate}}</div>
    </div>
  </div>
  <div class="customer">
    <strong>Bill To:</strong><br>
    {{customerName}}<br>
    {{customerEmail}}
  </div>
  <table>
    <thead>
      <tr><th>Item</th><th>Quantity</th><th>Unit Price</th><th>Total</th></tr>
    </thead>
    <tbody>
      {{#each items}}
      <tr>
        <td>{{this.name}}</td>
        <td>{{this.quantity}}</td>
        <td>\${{this.price}}</td>
        <td>\${{this.total}}</td>
      </tr>
      {{/each}}
      <tr class="total-row">
        <td colspan="3" style="text-align: right;">Total</td>
        <td>\${{grandTotal}}</td>
      </tr>
    </tbody>
  </table>
  {{#if notes}}
  <div style="margin-top: 20px; padding: 15px; background: #fffbeb; border-radius: 8px;">
    <strong>Notes:</strong> {{notes}}
  </div>
  {{/if}}
  <div class="footer">
    Thank you for your business. Payment is due within 30 days.
  </div>
</body>
</html>`;

const response = await fetch("https://api.cloudlayer.io/v2/template/pdf", {
  method: "POST",
  headers: {
    "X-API-Key": "your-api-key-here",
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    template: btoa(template),
    data: {
      companyName: "Acme Corp",
      invoiceNumber: "INV-2024-042",
      date: "2024-01-15",
      dueDate: "2024-02-14",
      customerName: "Jane Smith",
      customerEmail: "jane@example.com",
      items: [
        { name: "Web Development", quantity: 40, price: "150.00", total: "6,000.00" },
        { name: "Design Services", quantity: 20, price: "125.00", total: "2,500.00" },
        { name: "Hosting (Annual)", quantity: 1, price: "299.00", total: "299.00" },
      ],
      grandTotal: "8,799.00",
      notes: "Please include the invoice number in your payment reference.",
    },
    format: "letter",
    margin: { top: "0.5in", bottom: "0.5in", left: "0.5in", right: "0.5in" },
    printBackground: true,
    filename: "invoice-2024-042.pdf",
  }),
});

const pdf = await response.arrayBuffer();

Tips

  • Handlebars triple-braces: Use {{{variable}}} (triple curly braces) when your data contains HTML that should be rendered, not escaped. Use {{variable}} (double) for plain text to prevent XSS.
  • External resources: Custom templates can reference external CSS, fonts, and images via URLs. Make sure they are publicly accessible.
  • Template reuse: If you use the same template repeatedly, consider adding it to the template gallery via the dashboard and referencing it by templateId. This reduces payload size and simplifies your API calls.
  • Debugging templates: Test your Handlebars template locally using the Handlebars playground before sending it to the API.
  • Multipart vs. JSON: Use multipart uploads when your template file is large or when you want to avoid base64 encoding overhead.