Generating Dynamic Images at Scale

This tutorial shows how to generate Open Graph images, social cards, watermarked product photos, and branded placeholders — all dynamically via the Toolkit Image API with no headless browser required.

What you'll build

An image generation pipeline that: 1. Creates OG/social card images from HTML templates 2. Adds watermarks to product images 3. Generates placeholder images for prototypes 4. Converts all output to WebP for performance

Prerequisites

pip install httpx

Step 1: Generate social cards from templates

Create social card images using HTML+Liquid templates — ideal for blog posts, product pages, and landing pages:

import httpx
import base64

API_KEY = "YOUR_KEY"
BASE_URL = "https://image.toolkitapi.io/v1"

def generate_og_image(title: str, subtitle: str, output_path: str):
    """Generate an Open Graph social card image."""
    template = f"""
    <html>
    <body style="background: linear-gradient(135deg, #0f172a, #1e293b);
                 color: #f8fafc; font-family: system-ui, sans-serif;
                 display: flex; flex-direction: column;
                 align-items: center; justify-content: center;
                 width: 1200px; height: 630px; margin: 0; padding: 80px;">
      <div style="font-size: 14px; color: #3b82f6; margin-bottom: 24px;
                  letter-spacing: 2px; text-transform: uppercase;">
        Toolkit API
      </div>
      <h1 style="font-size: 56px; font-weight: 800; line-height: 1.2;
                 text-align: center; max-width: 900px; margin: 0 0 24px 0;">
        {{{{ title }}}}
      </h1>
      <p style="font-size: 24px; color: #94a3b8; text-align: center;
                max-width: 700px; margin: 0;">
        {{{{ subtitle }}}}
      </p>
    </body>
    </html>
    """

    r = httpx.post(
        f"{BASE_URL}/image/from-template",
        headers={"X-API-Key": API_KEY},
        json={
            "template": template,
            "variables": {"title": title, "subtitle": subtitle},
            "width": 1200,
            "height": 630,
            "dark_mode": True,
            "format": "png",
        },
        timeout=30.0,
    )
    r.raise_for_status()
    data = r.json()

    with open(output_path, "wb") as f:
        f.write(base64.b64decode(data["image"]))
    print(f"Social card saved to {output_path}")

# Generate social cards for multiple blog posts
posts = [
    ("Building a Domain Monitor", "Track DNS, WHOIS, and SSL changes with Toolkit API"),
    ("Bulk Email Validation at Scale", "Filter disposable addresses and check deliverability"),
]

for title, subtitle in posts:
    slug = title.lower().replace(" ", "-")
    generate_og_image(title, subtitle, f"og-{slug}.png")
const API_KEY = "YOUR_KEY";
const BASE_URL = "https://image.toolkitapi.io/v1";

async function generateOgImage(title, subtitle) {
  const template = `
    <html>
    <body style="background: linear-gradient(135deg, #0f172a, #1e293b);
                 color: #f8fafc; font-family: system-ui, sans-serif;
                 display: flex; flex-direction: column;
                 align-items: center; justify-content: center;
                 width: 1200px; height: 630px; margin: 0; padding: 80px;">
      <div style="font-size: 14px; color: #3b82f6; margin-bottom: 24px;
                  letter-spacing: 2px; text-transform: uppercase;">
        Toolkit API
      </div>
      <h1 style="font-size: 56px; font-weight: 800; text-align: center;
                 max-width: 900px; margin: 0 0 24px 0;">
        {{ title }}
      </h1>
      <p style="font-size: 24px; color: #94a3b8; text-align: center;
                max-width: 700px; margin: 0;">
        {{ subtitle }}
      </p>
    </body>
    </html>
  `;

  const r = await fetch(`${BASE_URL}/image/from-template`, {
    method: "POST",
    headers: { "X-API-Key": API_KEY, "Content-Type": "application/json" },
    body: JSON.stringify({
      template,
      variables: { title, subtitle },
      width: 1200,
      height: 630,
      dark_mode: true,
      format: "png",
    }),
  });
  const data = await r.json();
  return Buffer.from(data.image, "base64");
}

Step 2: Watermark product images

Add a logo or text watermark to protect product images:

def watermark_product(image_url: str, output_path: str):
    """Add a branded watermark to a product image."""
    r = httpx.post(
        f"{BASE_URL}/image/watermark/image",
        headers={"X-API-Key": API_KEY},
        json={
            "url": image_url,
            "watermark_url": "https://toolkitapi.io/logo-white.png",
            "position": "bottom-right",
            "opacity": 0.4,
            "scale": 0.12,  # 12% of the base image width
            "format": "webp",
        },
    )
    r.raise_for_status()
    data = r.json()

    with open(output_path, "wb") as f:
        f.write(base64.b64decode(data["image"]))

# Watermark a product catalog
watermark_product("https://cdn.example.com/product-1.jpg", "product-1-watermarked.webp")
watermark_product("https://cdn.example.com/product-2.jpg", "product-2-watermarked.webp")

Step 3: Generate branded placeholders

Create consistent placeholder images for mockups and prototypes:

def generate_placeholder(width: int, height: int, label: str,
                         bg_color: str, output_path: str):
    """Generate a branded placeholder image."""
    r = httpx.post(
        f"{BASE_URL}/image/placeholder",
        headers={"X-API-Key": API_KEY},
        json={
            "width": width,
            "height": height,
            "text": label,
            "bg_color": bg_color,
            "text_color": "#ffffff",
            "format": "webp",
        },
    )
    r.raise_for_status()
    data = r.json()

    with open(output_path, "wb") as f:
        f.write(base64.b64decode(data["image"]))

# Generate placeholders for a design system
placeholders = [
    (800, 600, "Hero Banner", "#3B82F6", "hero.webp"),
    (400, 300, "Card Image", "#10B981", "card.webp"),
    (1200, 400, "Header", "#8B5CF6", "header.webp"),
]

for w, h, label, color, path in placeholders:
    generate_placeholder(w, h, label, color, path)

Step 4: Convert everything to WebP

Optimize all images for web delivery:

def convert_to_webp(image_url: str, quality: int = 85) -> bytes:
    """Convert any image to optimized WebP format."""
    r = httpx.post(
        f"{BASE_URL}/image/convert",
        headers={"X-API-Key": API_KEY},
        json={
            "url": image_url,
            "format": "webp",
            "quality": quality,
        },
    )
    r.raise_for_status()
    data = r.json()
    return base64.b64decode(data["image"])

# Batch convert a directory of images
images = [
    "https://cdn.example.com/hero.png",
    "https://cdn.example.com/logo.jpg",
    "https://cdn.example.com/banner.gif",
]

for url in images:
    name = url.split("/")[-1].split(".")[0]
    webp_bytes = convert_to_webp(url)
    with open(f"{name}.webp", "wb") as f:
        f.write(webp_bytes)
    print(f"Converted {name} to WebP")

Complete pipeline

Here's a combined script that generates social cards, watermarks images, and optimizes everything:

import asyncio
import httpx

async def process_blog_post(client: httpx.AsyncClient, title: str,
                            subtitle: str, image_url: str, slug: str):
    """Generate all assets for a blog post."""

    # 1. Generate social card
    card_r = await client.post(
        f"{BASE_URL}/image/from-template",
        json={
            "template": SOCIAL_CARD_TEMPLATE,
            "variables": {"title": title, "subtitle": subtitle},
            "width": 1200, "height": 630, "format": "png",
        },
    )

    # 2. Watermark the hero image
    watermark_r = await client.post(
        f"{BASE_URL}/image/watermark/image",
        json={
            "url": image_url,
            "watermark_url": "https://toolkitapi.io/logo.png",
            "position": "bottom-right",
            "scale": 0.1, "format": "webp",
        },
    )

    # 3. Save results
    card_data = card_r.json()
    watermark_data = watermark_r.json()

    with open(f"{slug}-og.png", "wb") as f:
        f.write(base64.b64decode(card_data["image"]))
    with open(f"{slug}-hero.webp", "wb") as f:
        f.write(base64.b64decode(watermark_data["image"]))

    print(f"✓ {slug}: social card + watermarked hero generated")

# Run for all blog posts
async def main():
    async with httpx.AsyncClient(
        headers={"X-API-Key": API_KEY},
        timeout=30.0,
    ) as client:
        tasks = [
            process_blog_post(client, "Domain Monitoring Guide",
                            "Build an automated domain checker",
                            "https://cdn.example.com/monitor-hero.jpg",
                            "domain-monitor"),
            process_blog_post(client, "Email Validation Pipeline",
                            "Clean your mailing lists at scale",
                            "https://cdn.example.com/email-hero.jpg",
                            "email-pipeline"),
        ]
        await asyncio.gather(*tasks)

asyncio.run(main())

Endpoints used

Endpoint Purpose
POST /v1/image/from-template Render HTML templates as images
POST /v1/image/watermark/image Overlay logo watermarks
POST /v1/image/watermark/text Add text watermarks
POST /v1/image/placeholder Generate placeholder images
POST /v1/image/convert Convert between formats
POST /v1/image/compress Compress images for web

Taking it further

  • Blog automation — Hook into a CMS webhook to auto-generate social cards when a new post is published.
  • E-commerce — Watermark all product images on upload, then serve the WebP versions via a CDN.
  • Email campaigns — Generate personalized banner images by passing user names into HTML templates.
  • A/B testing — Generate multiple social card variants with different colors and text, then track click-through rates.

Browse all image endpoints → Browse processing endpoints →