HTMLCSStoImage API Timeout on Slow Websites: Troubleshooting Steps
Diagnose slow HTML/CSS to Image captures, choose the right render-wait control, and distinguish page delays from client errors and 429 limits.
When an HTML/CSS to Image capture of a slow website appears to time out, first record the endpoint, elapsed request time, HTTP status, response body, and whether the response included an image URL or ID. “Timeout” can mean the page was still loading when capture occurred, your client or integration returned an error, or the API returned a 429 usage or management-limit response. These are different problems and need different fixes.
The documented renderer waits for the page’s load event and monitors additional network traffic. Use ms_delay for a known extra wait, render_when_ready with ScreenshotReady() when your application can signal readiness, or max_wait_ms to cap the wait. The documented cap is 500–10,000 milliseconds; a low cap can produce an incomplete screenshot. The official docs do not establish a universal server-side timeout duration.
1. Identify which timeout you have
Before changing render settings or retrying, capture the facts for one failing request:
- Which endpoint and method did the client call?
- How many milliseconds elapsed before the client reported an error?
- What HTTP status and response body came back?
- Did the response contain an image URL or ID?
- Was the request an image-generation call or a management API operation?
A client-side deadline can expire while a server is still processing. A page can also finish loading after the capture point, leaving an incomplete image without a server timeout. A 429 is a rate or allowance response and should be diagnosed from its body and relevant headers; it does not show that the page rendered too slowly.
2. Understand how page readiness affects capture
HTML/CSS to Image documents that its renderer waits for the page load event, then monitors further network activity such as external CSS and images. A page with slow third-party assets can therefore take longer than a simple static page. Conversely, an application may inject important content after the load event, so a screenshot can be taken before that content appears.
The vendor FAQ says simple images can render in as little as 300 ms, most renders take 1–3 seconds, and complex pages may take longer. These are general vendor observations, not a guarantee, a service deadline, or an independent benchmark. [Source: HTML/CSS to Image FAQ.]
3. Choose the right wait control
| Control | Use it when | Trade-off |
|---|---|---|
ms_delay |
You know the page needs a brief extra pause after load, such as time for JavaScript or an animation. | A fixed delay may be too short on some pages and waste time on others. |
render_when_ready and ScreenshotReady() |
Your page code knows when all content needed in the screenshot is ready. | Requires the page to invoke the signal at the right point. |
max_wait_ms |
You want an upper bound on how long the renderer waits before capture. | When the cap is reached, content may still be missing. The documented maximum is 10,000 ms. |
ms_delay adds a delay; max_wait_ms caps waiting. They can be combined, with the maximum taking precedence. Raising max_wait_ms above 10,000 ms is outside the documented range. The documentation does not say that this parameter changes your HTTP client’s timeout or a separate server-side request deadline. [Sources: parameter reference and debugging guide.]
Use a fixed delay for a known late-rendering step
Start with the documented suggestion of 500 ms when JavaScript needs extra time, then increase only if representative captures show that required content is still missing. This adds predictable waiting even on pages that finish sooner.
Signal application readiness explicitly
If you control the page, set render_when_ready and call ScreenshotReady() from page JavaScript only after the content to capture is ready. This is usually clearer than guessing a delay for an application with asynchronous data or rendering. Consult the vendor’s integration instructions for the exact request setup and page-side code: render when ready.
Set a cap when slow resources should not hold up the capture
Set max_wait_ms between 500 and 10,000. A cap of a few seconds can help with slow pages, but the renderer can take the screenshot while essential content is still loading. Compare the resulting image at several values using representative target URLs. Do not assume the cap is a client timeout setting.
4. Reduce unnecessary work carefully
If you need only one region, use selector to capture a specific element rather than the whole page. This can reduce the amount of page content that matters to the output, but it does not guarantee that required resources will load sooner.
URL rendering also supports request_overrides to block matched requests; the vendor marks this option as requiring a paid plan. It may help when irrelevant requests keep the page active, but block only requests you have confirmed are unnecessary. Blocking a stylesheet, image, script, or API call needed by the selected region can make the capture incomplete or incorrect. Validate the image after each change. [Source: parameter reference.]
5. Diagnose HTTP 429 separately
The vendor documents two different limit paths:
- Image-generation credits: creating images consumes plan image credits. An exhausted allowance can return 429 with a plan-limit message. Waiting 60 seconds does not restore consumed credits.
- Management API throttles: resource read and write operations have separate per-minute limits. The documented limits are 100 read requests and 20 write requests per minute for the applicable organization and operation groups. A throttle identifies its group and may include
Retry-After. Follow that header when present; otherwise the vendor instructs waiting 60 seconds.
The vendor says image generation has no per-second or per-minute request limit, but it is still subject to plan image credits. Do not treat the management API throttle as an image-creation limit. [Sources: FAQ and API rate limits.]
For image-credit diagnosis, inspect x-renders-allowed, x-renders-consumed, and x-renders-used response headers, or use the documented usage methods. GET /v1/usage reports image history, not management request counts. [Source: usage documentation.]
6. Runnable request examples
The following examples show how to record the status, response body, and elapsed time for a request. They use the image endpoint and basic request shape; consult the vendor’s API documentation for authentication and the exact parameter encoding supported by your account. The examples do not assume a universal server timeout. Set a client timeout that fits your own application and record client timeout exceptions distinctly from HTTP responses.
cURL
curl -sS -D response-headers.txt -o response-body.bin \
-w 'HTTP %{http_code}\nTotal seconds %{time_total}\n' \
-H 'Content-Type: application/json' \
-d '{"html":"<h1>Capture me</h1>","ms_delay":500,"max_wait_ms":5000}' \
'https://hcti.io/v1/image'
Check response-headers.txt for the status and any usage headers, then inspect response-body.bin. Use the request URL, authentication, and payload format configured for your account.
Python
import time
import requests
endpoint = "https://hcti.io/v1/image"
payload = {
"html": "<h1>Capture me</h1>",
"ms_delay": 500,
"max_wait_ms": 5000,
}
started = time.monotonic()
try:
response = requests.post(endpoint, json=payload, timeout=(5, 45))
elapsed = time.monotonic() - started
print("status:", response.status_code)
print("elapsed_seconds:", round(elapsed, 3))
print("body:", response.text[:2000])
for name in ("x-renders-allowed", "x-renders-consumed", "x-renders-used", "Retry-After"):
if name in response.headers:
print(f"{name}:", response.headers[name])
except requests.Timeout as exc:
elapsed = time.monotonic() - started
print("client_timeout_seconds:", round(elapsed, 3))
print("error:", exc)
The 5-second connect and 45-second read values are example client settings, not HTML/CSS to Image server limits. Choose values for your caller and report a client timeout as such.
Node.js
const endpoint = 'https://hcti.io/v1/image';
const payload = {
html: '<h1>Capture me</h1>',
ms_delay: 500,
max_wait_ms: 5000,
};
const started = Date.now();
try {
const response = await fetch(endpoint, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(payload),
signal: AbortSignal.timeout(45000),
});
console.log('status:', response.status);
console.log('elapsed_ms:', Date.now() - started);
console.log('body:', (await response.text()).slice(0, 2000));
for (const name of ['x-renders-allowed', 'x-renders-consumed', 'x-renders-used', 'retry-after']) {
const value = response.headers.get(name);
if (value !== null) console.log(`${name}:`, value);
}
} catch (error) {
console.log('client_timeout_or_network_error_ms:', Date.now() - started);
console.log('error:', error.message);
}
The 45-second abort is an example caller-side deadline only. Configure authentication and request details as required by your account.
7. Common errors and fixes
| Symptom | Likely cause | What to do |
|---|---|---|
| Client reports a timeout, but no HTTP status or body is available. | The caller’s own connection or response deadline expired, or a network issue interrupted the request. | Record the client exception and elapsed time. Check connectivity and caller timeout settings; do not label it a confirmed renderer timeout. |
| Image is returned, but JavaScript content is absent. | The content appeared after the capture point. | Try a modest ms_delay, or signal application readiness with render_when_ready and ScreenshotReady(). |
Screenshot is incomplete after setting max_wait_ms. |
The cap was reached while important content or assets were still loading. | Increase the cap within the documented 10,000 ms maximum, use explicit readiness signaling, and verify assets. A cap is allowed to capture incomplete output. |
| Capture remains slow despite a delay. | External assets or other page work remain slow; a fixed delay adds time but does not make those resources faster. | Inspect the page’s required network resources. Consider a selector or carefully validated request overrides for irrelevant requests. |
| 429 with a plan-limit message. | Image-generation credits are exhausted or unavailable for the plan. | Check render allowance and usage headers or usage methods. Retrying after a minute does not replenish image credits. |
| 429 identifies a management operation group. | A per-minute read or write throttle was reached. | Honor Retry-After when present; otherwise wait the documented 60 seconds before retrying that management operation. |
8. Performance, reliability, and cost considerations
Waiting longer may improve completeness when the required content is genuinely late, but every extra wait can increase end-to-end latency. A shorter cap can improve throughput while increasing the chance of missing content. Test settings against representative slow pages, including the slow assets and asynchronous content that matter to your output.
For reliability, log endpoint, status, elapsed time, response body or error class, returned image ID or URL, and relevant usage or retry headers. Avoid blindly retrying a 429: first identify whether it is a credit exhaustion or a management throttle. For client timeouts, distinguish connect failures from response-read deadlines where your HTTP library supports that distinction.
For cost, image-generation calls consume plan image credits; management API throttles are a separate concern. A failed client connection does not by itself tell you whether the server completed the render, so use the response and usage evidence available before issuing duplicate work. The reviewed docs do not provide a universal server-side timeout or establish billing behavior for every client-side failure case.
Or skip the browser setup
If the goal is to capture a website without configuring a browser renderer, ScreenshotNeo provides a one-request website screenshot API. Its API documentation covers the request options.
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}`);
ScreenshotNeo accepts cookie or consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed. Its MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs. The Free plan includes 1,000 shots a month with no card; paid plans start at $5 for 3,000 shots.
Sign up free for 1,000 screenshots a month with no card.
9. Diagnostic checklist and escalation
- Save the endpoint, request parameters, elapsed time, HTTP status, response body, and returned image ID or URL.
- Classify the result as caller/network timeout, incomplete capture, image-credit 429, or management-throttle 429.
- For late content, try a measured
ms_delayor userender_when_readywith a correctly timedScreenshotReady()signal. - If using
max_wait_ms, keep it within 500–10,000 ms and inspect the resulting image for missing content. - For a 429, check the error body and relevant headers before retrying; honor
Retry-Afterfor management throttles. - If the cause remains unclear, send the provider a reproducible request, timestamp, status and response body, elapsed time, and relevant headers. Remove API credentials and private page data first.
FAQ
What does max_wait_ms do?
It caps how long the renderer waits before taking the screenshot. The documented range is 500–10,000 ms. It can produce an incomplete image if needed content has not loaded by the cap.
Should I set max_wait_ms to 10 seconds for every page?
No. Use representative captures to find a suitable cap. A longer wait can add latency, and the cap does not make slow resources finish sooner.
Does a 429 mean my website was too slow?
No. Check the message and operation: image generation can run out of plan credits, while management operations have separate per-minute throttles.
Can I wait longer than 10 seconds with max_wait_ms?
The documented maximum is 10,000 ms. The reviewed documentation does not establish a separate universal server-side timeout that can be changed with this parameter.


