HTML and CSS to Image API Examples
Working HTML/CSS-to-image API examples for cards, webpages, PDFs, templates, and production integrations in cURL, Python, and Node.js.
Use a rendering API when you need a reliable image or PDF from HTML and CSS without running a browser yourself. Send either an HTML document, a public URL, or template values to POST https://hcti.io/v1/image with HTTP Basic Auth. The JSON response includes a generated image URL and image ID. You can request the result as PNG, JPG, WebP, or PDF.
This guide covers direct HTML/CSS rendering, webpage screenshots, reusable templates, Open Graph images, PDF output, authentication, production parameters, complete cURL/Python/Node.js examples, troubleshooting, performance, and an API alternative for teams that want clean webpage captures.
1. How the HTML/CSS to image API works
- Create HTML for the content and CSS for its presentation, or provide a fully qualified public URL.
- POST the payload to
https://hcti.io/v1/imageusing your API ID as the Basic Auth username and API key as the password. - Read the JSON response, which contains a generated URL and image ID.
- Use the returned URL with
.png,.jpg,.webp, or.pdfas the output format requires.
The API accepts an HTML snippet or complete document. If both url and html are sent, the URL takes precedence; CSS can still be injected into that URL capture.
2. Render HTML and CSS into an image
This is the simplest workflow for cards, badges, invoices, certificates, social graphics, and email previews.
cURL
curl -u "$HCTI_API_ID:$HCTI_API_KEY" \
-X POST "https://hcti.io/v1/image" \
-H "Content-Type: application/json" \
-d '{
"html": "<div class=\"box\">Hello, world!</div>",
"css": ".box { padding: 20px; background: #03B875; color: white; font: 700 32px Arial; }",
"google_fonts": "Roboto",
"device_scale": 2
}'
Python
import os
import requests
payload = {
"html": "Hello, world!",
"css": ".box { padding: 20px; background: #03B875; color: white; font: 700 32px Arial; }",
"google_fonts": "Roboto",
"device_scale": 2,
}
response = requests.post(
"https://hcti.io/v1/image",
auth=(os.environ["HCTI_API_ID"], os.environ["HCTI_API_KEY"]),
json=payload,
timeout=90,
)
response.raise_for_status()
result = response.json()
print(result["url"])
print(result["id"])
Node.js
const apiId = process.env.HCTI_API_ID;
const apiKey = process.env.HCTI_API_KEY;
const response = await fetch('https://hcti.io/v1/image', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Basic ' + Buffer.from(`${apiId}:${apiKey}`).toString('base64')
},
body: JSON.stringify({
html: '<div class="box">Hello, world!</div>',
css: '.box { padding: 20px; background: #03B875; color: white; font: 700 32px Arial; }',
google_fonts: 'Roboto',
device_scale: 2
})
});
if (!response.ok) throw new Error(`${response.status} ${await response.text()}`);
const result = await response.json();
console.log(result.url, result.id);
Keep both credentials on your server. Never put the API key in browser JavaScript, a mobile app bundle, or a public repository.
3. Convert a public webpage to an image
Send a fully qualified, publicly reachable URL. This captures the rendered page rather than requiring you to copy its markup.
curl -u "$HCTI_API_ID:$HCTI_API_KEY" \
-X POST "https://hcti.io/v1/image" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/pricing",
"css": "body { background: #ffffff !important; }",
"full_screen": true,
"device_scale": 2,
"ms_delay": 1500,
"max_wait_ms": 20000,
"media_type": "screen"
}'
Useful URL-capture controls include:
| Parameter | Use |
|---|---|
full_screen |
Capture the entire page height instead of only the viewport. |
headers |
Add request headers when the origin permits them. |
ms_delay |
Wait a fixed number of milliseconds for client-side rendering. |
max_wait_ms |
Set the maximum wait time for page readiness. |
media_type |
Select screen or print CSS. |
device_scale |
Choose pixel density from 0.1 to 3. Higher values increase detail and file size. |
google_fonts |
Load one or more Google Fonts, separated with |. |
jumbo_max_width and jumbo_max_height |
Request very large output dimensions, up to 80,000 pixels when both are set. These consume additional image credits. |
4. Choose PNG, JPG, WebP, or PDF
PNG is the default. JPG and WebP are served from the stored PNG result. PDF is rendered and saved separately, so it is not merely a file extension conversion of the PNG.
Request a different image format
curl -u "$HCTI_API_ID:$HCTI_API_KEY" \
-X POST "https://hcti.io/v1/image" \
-H "Content-Type: application/json" \
-d '{"html":"<h1>Quarterly report</h1>","format":"webp"}'
You can also take the returned URL and request its .png, .jpg, or .webp form when your integration needs a specific content type.
Generate a PDF
curl -u "$HCTI_API_ID:$HCTI_API_KEY" \
-X POST "https://hcti.io/v1/image" \
-H "Content-Type: application/json" \
-d '{
"html": "<main><h1>Invoice 1042</h1><p>Balance: $240</p></main>",
"css": "@page { margin: 18mm; } body { font-family: Arial; }",
"format": "pdf",
"pdf_options": {
"paper_size": "A4",
"margin": "18mm",
"landscape": false,
"print_background": true,
"scale": 1
}
}'
Use pdf_options for paper size, margins, scale, landscape orientation, background printing, and page-related settings. For a URL capture, you can request a PDF URL after the render or set the format to pdf.
5. Fonts, viewport, cropping, and large output
- Device scale: Start at
1for normal output and use2for sharper retina assets. Values up to3increase pixel count and file size. - Google Fonts: Set
google_fontsto one family or several separated by|. Define matching font-family rules in your CSS. - Viewport and full screen: Use the viewport controls for a fixed browser-sized capture and
full_screenfor the entire page height. - Cropping: Use the documented crop or viewport fields when you need a specific region rather than the whole render.
- Jumbo images: Set both
jumbo_max_widthandjumbo_max_heightfor outputs up to 80,000 pixels. Plan for extra image credits and larger downloads.
6. Reusable templates
When the design stays fixed and only values change, create a template and POST template_values to https://hcti.io/v1/image/:template_id. This avoids sending the complete layout for every render.
curl -u "$HCTI_API_ID:$HCTI_API_KEY" \
-X POST "https://hcti.io/v1/image/YOUR_TEMPLATE_ID" \
-H "Content-Type: application/json" \
-d '{
"template_values": {
"title": "March revenue",
"revenue": "$84,200",
"growth": "+18%"
}
}'
Templates are useful for reports, catalog cards, social images, and personalized certificates where layout changes are rare but data changes on every request.
7. Open Graph images and social cards
Automatic Open Graph image generation is a supported workflow. A common architecture is:
- Store the page title, description, author, and theme in your application.
- Render those values into a fixed HTML/CSS template.
- Generate the image when content is published or when metadata changes.
- Use the generated URL in
og:imageand related social metadata.
Generate once and cache the result when the content is immutable. Regenerate when the underlying title, image, or visual theme changes.
8. Authentication and signed delivery
The API uses HTTP Basic authentication: API ID as the username and API key as the password. Treat the key like a password and keep it server-side.
For on-demand images where a browser must request an image without receiving your API key, use signed image URLs. The signing scheme uses an HMAC SHA256 token; official clients can generate signed URLs for you. A signed URL lets your frontend embed or request a specific image while the credential remains on your server.
9. Production integration checklist
- Keep API credentials in environment variables or a secret manager.
- Validate and constrain user-supplied URLs before sending them to a renderer.
- Set an explicit request timeout and handle non-2xx responses.
- Use a stable viewport, font stack, and device scale so repeated renders are comparable.
- Wait for client-side content with
ms_delayand cap total waiting withmax_wait_ms. - Cache immutable results by a hash of the input HTML/CSS or template values.
- Use PNG for lossless text and diagrams, WebP for smaller web delivery, JPG for photographic content, and PDF for documents.
- Log the request type, output format, response status, latency, and returned image ID. Do not log API keys.
- Retry transient network failures with bounded exponential backoff. Do not blindly retry invalid payloads or authentication failures.
- For large images, stream the returned file to object storage instead of buffering multiple copies in memory.
10. Common errors and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| 401 or 403 response | Missing, reversed, or invalid Basic Auth credentials. | Use the API ID as the username and API key as the password; verify environment variables and remove accidental whitespace. |
| HTML is ignored | A url field was also supplied. |
Remove url for HTML rendering, because URL input takes precedence. |
| Blank or incomplete page | JavaScript had not finished before capture. | Increase ms_delay, raise max_wait_ms, or provide a stable server-rendered page. |
| Fonts fall back | The font was not loaded or the CSS family name does not match. | Set google_fonts, use the exact family in CSS, and allow enough wait time. |
| Page is clipped | The capture is limited to the viewport. | Use full_screen for a full-height URL capture or set explicit dimensions and cropping. |
| Output is blurry | Pixel density is too low for the display size. | Increase device_scale up to 3, then check the resulting file size. |
| Huge file or slow download | Large dimensions, high device scale, or jumbo settings. | Reduce dimensions or scale, choose WebP, and avoid jumbo output unless it is required. |
| Private URL cannot render | The renderer cannot reach an authenticated or internal origin. | Expose a controlled public route or use headers supported by the origin; never embed credentials in a URL. |
| PDF layout differs from the image | PDF is a separate render and may use print CSS. | Set media_type, pdf_options, margins, scale, and background printing explicitly. |
| Rate or credit errors | Request volume or jumbo dimensions exceed the account allowance. | Reduce concurrency, cache results, and review image dimensions and account limits. |
11. Performance, reliability, and cost considerations
Rendering time is affected by page JavaScript, external fonts, network resources, viewport size, device scale, and full-page height. Keep HTML self-contained when possible, avoid unnecessary third-party assets, and use a bounded readiness wait. High device scales and jumbo dimensions increase both processing work and output size.
For reliable jobs, make requests idempotent in your application by deriving a content hash from the input. Store the returned URL and image ID, and only regenerate when that hash changes. Queue non-interactive work such as social cards and reports so user-facing requests do not wait on every render.
The documentation describes implementation parameters rather than universal latency or cost benchmarks. Confirm your account’s current allowance and pricing before committing to a high-volume workload.
12. Or skip the browser setup
If your goal is a clean screenshot of a live webpage rather than rendering your own HTML, ScreenshotNeo provides a single GET request for PNG, JPEG, WebP, or PDF output. Its capture options include full-page screenshots with lazy images loaded, element selection, dark mode, device presets, custom CSS and JavaScript, waits, request blocking, headers, cookies, user agents, timezone and geolocation, resizing, caching, signed links, asynchronous jobs, bulk capture, and an MCP server for AI agents.
Here is the minimal call. See the ScreenshotNeo API documentation for the complete option list.
cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' }); const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots a month with no card, and paid plans start at $5 for 3,000 shots.
Create a free ScreenshotNeo account and start with 1,000 screenshots a month at no charge.
13. FAQ
Can I send a complete HTML document?
Yes. The html field accepts a snippet or a full document. Include the styles inline or in the css field.
Does a URL override HTML?
Yes. When url is supplied, it takes precedence over html. CSS can still be injected into the URL capture.
Which format should I use for a social card?
PNG is a safe default for crisp text. WebP is usually a better delivery choice when smaller files are more important than maximum compatibility.
Is PDF just a renamed PNG?
No. PDF is rendered and saved separately, with its own paper, margin, scale, orientation, and print-background settings.
How do I keep API credentials out of a frontend?
Call the API from your server, or generate a signed image URL server-side and give the browser only that signed URL.
When should I use a template?
Use a template when the layout is stable and only values such as title, revenue, or growth change between renders.


