Paste a link to a blog post into Slack, LinkedIn or iMessage and the preview is an image chosen by the page's og:image tag. If you have one generic image for the whole site, every share looks the same and nobody can tell your posts apart. If each post has its own card with its title on it, people click.
Nobody designs those cards by hand for five hundred pages. You write one HTML template, fill it with each page's data, and render it to a PNG. The same trick produces order confirmations for messaging apps that will not display HTML, weekly summary images for a team chat, and certificate-style badges. Two APIs do the rendering: HTML to Image takes HTML, and Markdown to Image takes Markdown and styles it for you.
Your first card
htmltoimage is a POST with a JSON body. html is required. width and height set the viewport in pixels (defaults 1280 by 800), and format picks the output. Open Graph cards are conventionally 1200 by 630, which is a 1.91:1 ratio most platforms crop cleanly.
curl -X POST "https://api.apiverve.com/v1/htmltoimage" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"html":"<div style='width:1200px;height:630px;display:flex;align-items:center;padding:0 80px;box-sizing:border-box;background:#0f172a;color:#fff;font:700 64px sans-serif'>Invoice #1042 is paid</div>","width":1200,"height":630}'Open downloadURL to see the card. width, height and htmlLength echo back what was rendered.
The response gives you everything you need to use or verify the result:
| Field | What it tells you |
|---|---|
downloadURL | Where to fetch the image. Temporary. |
expires | When that link dies, as a Unix timestamp in milliseconds (about two days out). |
format | The format you actually got. |
width, height | The dimensions that were rendered. |
htmlLength | How many characters of HTML were received. |
imageName | The generated file name. |
Read format, width and height back rather than assuming. Custom dimensions and a non-PNG format are paid-plan options; on a plan without them you get the 1280 by 800 PNG default, and a card designed for 1200 by 630 will render with empty space. format accepts png and jpeg (jpg is treated as jpeg). Asking for webp currently gives you a PNG, which you will only notice if you check data.format.
htmlLength is more useful than it looks. If it is much smaller than the string you built, something truncated your template before it was sent, usually a templating bug or a proxy limit. Logging it next to the length you sent is a one-line sanity check.
Design the template for a fixed canvas
A web page flows; an image does not. The viewport is exactly width by height, and anything outside it is cut off. That changes how you write the HTML.
Size the root element explicitly. Give your outer element the same pixel dimensions you pass as width and height, use box-sizing: border-box, and lay the content out with flexbox or grid inside it. Percentages of an unknown body are how you end up with a card that is 80% white margin.
Make the HTML self-contained. Put the CSS in a <style> block or inline attributes. A stylesheet or font that loads from another server is one more thing that can be slow or missing at render time, and a missing font silently falls back to a default face. If brand typography matters, embed the font as a data URI or accept a carefully chosen system font stack.
Plan for long text. Titles vary from "Pricing" to "How we cut our cold-start latency by 70% on Cloud Run without minimum instances". Clamp lines with CSS (display: -webkit-box; -webkit-line-clamp: 3; overflow: hidden) and pick a font size that survives the worst title in your data. Better still, shrink the font for long titles in your own code before you render.
Render at the size it will be displayed. Social platforms downscale. Text that is 18 px in a 1200-pixel card is unreadable in a phone feed. Headline text on a card should be at least 48 px.
Your card template uses `<body style='margin:0'>` and a title div with `width: 80%`. Cards come out with the text squeezed into the top-left corner and a lot of empty space. What is the most reliable fix?
Answer the check above to continue.
Markdown when you do not want to write a template
Sometimes you do not care about brand layout. You want release notes, a status summary or a short report as a picture, because the destination is a chat channel, a kiosk display or a messaging app that will not render rich text. Writing a full HTML template for that is overkill.
markdowntoimage takes the same width, height and format parameters as htmltoimage, but the main input is markdown. It converts the Markdown to HTML, wraps it in a clean default stylesheet (system sans-serif, readable line height) and renders that.
curl -X POST "https://api.apiverve.com/v1/markdowntoimage" \
-H "x-api-key: YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{"markdown":"# Release 2.4\n\n- Faster exports\n- **New** OCR endpoint\n- Fixed: timezone bug in reports"}'markdownLength is what you sent; htmlLength is the HTML it became before rendering.
The response reports markdownLength (what you sent) and htmlLength (the HTML generated from it). On this API width and height in the response are premium fields, so do not depend on them for verification here; markdownLength is the equivalent sanity check.
Use Markdown when the content is the point and the look is secondary: weekly metrics posted to a team channel, a changelog image for a social post, a rendered checklist. Switch to HTML the moment someone asks for a logo in the corner, specific colours, or two columns.
Markdown has its own injection surface, smaller but real. If you put user text into a Markdown document, characters like #, *, [ and backticks change the formatting. Escape them with a backslash, or keep user text in places where formatting cannot hurt, such as list items you control.
Receipts and confirmations as images
A receipt rendered as an image is the right answer in a narrow set of situations: a messaging channel that will not display HTML, a kiosk or a printer that wants a bitmap, or a confirmation screen a customer will screenshot anyway. Render it at a narrow width (receipt printers and phone screens are tall and thin) and generous font sizes.
It is the wrong answer as your only record. Text inside an image cannot be searched, copied or read by a screen reader, and an accounting system cannot parse it. Send the image alongside real text: the email body still contains the order number, amounts and dates as text, and the image carries alt text summarising them. If the customer needs a document to keep, generate a PDF, which keeps the text layer.
A product manager wants order confirmation emails to be a single rendered image of the receipt, with no text in the body, so they look identical in every email client. What is the strongest objection?
Cost and failure handling
Both APIs cost 20 credits per call, double the converters, because each call runs a browser render. With caching keyed on content, the cost tracks how often your content changes, not how often it is viewed.
When a render fails, fall back rather than break the page. For OG cards, keep a generic site-wide image and use it whenever your card is missing; a share with a generic preview is fine, a share with no preview is not. For anything a person is waiting on, set the 30-second timeout and show a placeholder.
So far you have converted images and created them. The next lesson reads them: pulling text out of photos, receipts and scanned documents with OCR, and cleaning up what comes back: Extract text with OCR.