Documentation
Testing and Debugging
Production renders go wrong for predictable reasons. This page walks through how to reproduce them locally and how to read the API response when something fails.
Inspecting the rendered HTML before submitting
Render the HTML and CSS in your browser at the exact viewport size before sending it to the API. This catches layout bugs before they consume credits.
- Save your HTML to a local file (e.g.
preview.html). - Open Chrome DevTools (F12) and toggle the device toolbar (Cmd+Shift+M on macOS).
- Set the dimensions to match the
widthandheightyou plan to send to the API. The default is 1440x900; for an OG image use 1200x630. - Verify fonts load. If a custom font shows as Times or system default in your browser, it will fall back the same way in the API.
- Take a real Chrome screenshot with the DevTools “Capture screenshot” command and compare it to what you expect.
If the local Chrome render looks correct, the API render will match. The API uses real Chrome with the same flags.
Common reasons rendering differs from your browser
When the API output and your local render do not match, the cause is almost always one of these:
- Missing fonts. Your machine has the font installed, the API server does not. Use Google Fonts via
<link>or self-host the font with a public URL and an@font-facerule. - JS execution timing. Your JavaScript runs after a delay or waits for a network event. Use
wait_for_selectororms_delayso the API captures after the content paints. See wait_for_selector. - CSS that depends on user agent. Hover, focus, prefers-color-scheme, and viewport-relative units behave differently in headless mode. Test using the same viewport with
width/height. - Third-party resources blocked or slow. Ad networks, analytics scripts, and cross-origin assets may time out within the 30 second budget. Inject CSS with
display: nonefor non-essential elements via the css parameter.
Webhook testing
For renders that exceed the 30 second sync budget, use webhook_url. During development you do not need a public server.
- webhook.site - get a free temporary URL, paste it into your
webhook_urlfield, and inspect the JSON payload that arrives. - ngrok - expose your local server with
ngrok http 3000. Use the printedhttps://...ngrok.ioURL as yourwebhook_url. Ngrok’s request inspector shows headers and body for every delivery.
A successful webhook payload looks like:
{
"success": true,
"id": "abc123",
"url": "https://i.html2img.com/abc123.png"
}
Reading the response
The four most common responses, what they mean, and how to fix.
Success (200)
{
"success": true,
"credits_remaining": 95,
"id": "abc123",
"url": "https://i.html2img.com/abc123.png"
}
The render finished. Fetch the PNG from url or use it directly as an <img src>.
Validation error (422)
{
"error": "Validation failed",
"code": "validation_error",
"details": {
"html": ["The html field is required."]
}
}
A required field is missing or out of range. The details object names the field and the rule that failed.
Timeout (504)
{
"error": "Request timed out",
"code": "timeout_error",
"message": "The request timed out while processing.",
"id": "abc123"
}
The render took longer than 30 seconds on the sync path. Switch to webhook_url, or reduce DPI, or remove slow third-party assets.
Service error (500)
{
"error": "Service error",
"code": "service_error",
"message": "An unknown error occurred. Please try again.",
"id": "abc123"
}
A bug on our side. Retry once. If it persists, contact support via /contact and quote the id.
Websites change their HTML structure over time. Selectors that work today may break tomorrow. Re-test selectors when a third-party site updates.