TL;DR

  • → Generate unique OG images for every post, product, or user profile via a REST API
  • → Write once in HTML, render consistently every time via headless Chrome
  • → 1 token per image (~$0.016). 500 OG images costs less than $10

HTML to Image API — OG Image Generation

Open Graph images determine how your links look on Twitter, LinkedIn, Slack, and iMessage. A unique, well-designed OG image can 2–3× your click-through rate. The problem: generating them manually for every post or product doesn't scale. The solution: an HTML template and one API call.

Why HTML → PNG for OG images?

Step 1 — Design your template

Write the OG image as a 1200×630 HTML div with inline styles. Include a gradient background, your tag/category pill, the title, and author line:

def og_template(title: str, author: str, date: str, tag: str) -> str:
    return f"""
    <div style="
      width:1200px; height:630px;
      background:linear-gradient(135deg,#1e1b4b,#312e81);
      display:flex; flex-direction:column;
      justify-content:center; padding:80px;
      font-family:'Inter',sans-serif; color:white;
      box-sizing:border-box;
    ">
      <span style="
        background:rgba(99,102,241,0.3); color:#a5b4fc;
        padding:6px 16px; border-radius:999px;
        font-size:14px; width:fit-content; margin-bottom:24px;
      ">{tag}</span>
      <h1 style="
        font-size:54px; font-weight:800;
        line-height:1.1; margin:0 0 24px; max-width:900px;
      ">{title}</h1>
      <p style="font-size:20px; color:#a5b4fc; margin:0">
        {author} · {date}
      </p>
    </div>
    """

Step 2 — Call the API

import os, requests

def generate_og_image(title: str, author: str, date: str, tag: str) -> str:
    """Render an OG image and return a permanent CDN URL."""
    r = requests.post(
        "https://www.agent-gen.com/api/v1/generate/image",
        headers={
            "X-API-Key": os.environ["AGENTGEN_API_KEY"],
            "Content-Type": "application/json",
        },
        json={
            "html": og_template(title, author, date, tag),
            "width": 1200,
            "height": 630,
            "format": "png",
        },
        timeout=30,
    )
    r.raise_for_status()
    return r.json()["url"]

# Single image
url = generate_og_image(
    title="How to Build an AI Invoice Generator",
    author="Yair Levi",
    date="March 2026",
    tag="Tutorial",
)
print(url)  # https://cdn.agent-gen.com/output/abc123.png

Step 3 — Generate in bulk with asyncio

import asyncio, aiohttp, os

async def generate_og_async(session: aiohttp.ClientSession, post: dict) -> tuple[str, str]:
    async with session.post(
        "https://www.agent-gen.com/api/v1/generate/image",
        headers={"X-API-Key": os.environ["AGENTGEN_API_KEY"]},
        json={
            "html": og_template(post["title"], post["author"], post["date"], post["tag"]),
            "width": 1200, "height": 630, "format": "png",
        },
    ) as resp:
        data = await resp.json()
        return post["slug"], data["url"]

async def backfill_all_og_images(posts: list[dict]) -> dict[str, str]:
    async with aiohttp.ClientSession() as session:
        results = await asyncio.gather(
            *(generate_og_async(session, p) for p in posts)
        )
    return dict(results)  # {slug: image_url}

# Backfill 500 blog posts concurrently
og_urls = asyncio.run(backfill_all_og_images(all_posts))

Integrate with Next.js

Call generate_og_image() at build time in generateStaticParams, or on-demand in a Route Handler. Cache the resulting URL in your database to avoid re-generating on every request.

500 OG images = 500 tokens = less than $10 at any tier. Tokens never expire, so you can backfill your entire archive and use leftover tokens for future posts.

Generate your first OG image in 2 minutes

50 free tokens on signup — that's 50 unique OG images at no cost.