Fix Browserless Screenshot API Timeouts on Slow Websites
Find which Browserless timeout is ending your screenshot request, then match navigation and content waits to what the page actually needs.
Start by finding which timeout expires. A Browserless REST screenshot can have an overall request timeout, a navigation timeout, and a content-readiness wait. Increasing the wrong one may not help: for example, a navigation that waits for network idle can stall on a page whose requests never settle, even when the content you need is already visible.
For the current REST endpoint, send a POST request to /screenshot with the target URL and screenshot configuration in the JSON body. Set the overall timeout query parameter high enough for navigation and configured waits, set gotoOptions.timeout for navigation, and use a selector or function wait for the content that must appear. The exact options available can vary by endpoint generation and account plan; check the [Browserless Screenshot API guide](https://docs.browserless.io/rest-apis/screenshot) and [request configuration reference](https://docs.browserless.io/rest-apis/request-configuration) for your route.
1. Identify which timeout is expiring
Before changing values, record the exact error, HTTP status, elapsed time, endpoint hostname and route, request body, and whether the failure occurs during navigation, a content wait, or image capture. Redact your token before sharing logs. Similar-looking timeout failures can come from different budgets.
| Timeout layer | What it limits | Typical adjustment |
|---|---|---|
| Overall REST request | The whole request, including configured waits. | Raise the timeout query parameter enough to cover navigation and readiness waits. |
| Navigation | Loading the URL under gotoOptions. |
Set gotoOptions.timeout; reconsider waitUntil if a lifecycle event is too demanding. |
| Content readiness | Waiting for a selector, function, or event. | Set an appropriate wait timeout and check whether the condition can actually become true. |
| Fixed delay | A configured period of elapsed time. | Use waitForTimeout only for known delays such as a transition. |
| Session or launch | How long the browser session may remain open. | Check the route and plan ceiling; a larger request timeout does not guarantee a larger session limit. |
| Screenshot operation | The screenshot mutation in BrowserlessQL. | Applies to BrowserlessQL’s screenshot.timeout, not the REST /screenshot default. |
Browserless documents the global timeout as applying to the whole request, including waits. The BrowserlessQL screenshot mutation separately documents a 30,000 ms screenshot-operation default. Do not apply that BrowserlessQL field to REST requests. Likewise, the launch documentation describes a 60,000 ms default session timeout and a plan-dependent maximum. These are distinct settings, not a universal timeout value for every endpoint. See [timeout configuration](https://docs.browserless.io/rest-apis/timeouts), [launch parameters](https://docs.browserless.io/baas/launch-options), and the [BrowserQL screenshot mutation](https://docs.browserless.io/browserql/screenshot).
2. Make the navigation condition less demanding when appropriate
gotoOptions.waitUntil determines which browser lifecycle condition counts as navigation complete. Documented values for relevant REST schemas include domcontentloaded, load, networkidle0, and networkidle2. A page with analytics, polling, ads, or other continuing requests may not reach a network-idle condition promptly.
If the page is usable before its network activity stops, try a less demanding lifecycle condition supported by your route, such as domcontentloaded. Then wait for the meaningful content your screenshot needs, such as a page heading or chart container. This avoids treating a quiet network as proof that the content is ready.
Do not change the lifecycle condition blindly. If the required content is populated only after scripts finish, capture after an appropriate selector or function condition instead. A fixed delay can cover a known animation or transition, but it only adds time; it does not confirm readiness.
3. Give navigation and content readiness separate budgets
Make the outer request timeout larger than the time you reasonably allow for navigation plus content readiness and screenshot generation. Keep each inner budget finite. If a selector never appears, a longer outer timeout alone will not make it appear.
The following request illustrates the current REST shape: POST to /screenshot, JSON body containing the URL and screenshot configuration, and a query timeout for the full request. Replace the host, token, target, and selector with your own. Confirm the exact JSON fields against the documentation for your endpoint before using the example.
curl -X POST 'https://YOUR_BROWSERLESS_HOST/screenshot?token=YOUR_API_TOKEN&timeout=90000' \
-H 'Content-Type: application/json' \
--data '{
"url": "https://example.com/slow-page",
"options": {
"type": "png",
"gotoOptions": {
"waitUntil": "domcontentloaded",
"timeout": 30000
},
"waitForSelector": {
"selector": "main h1",
"timeout": 20000
}
}
}' \
--output screenshot.png
In this example, navigation may use up to 30 seconds and the selector wait up to 20 seconds, within a 90-second overall request budget. These are example values, not Browserless defaults or a recommended value for every site. If your route represents screenshot options differently, follow its schema; the key is to assign a budget to each stage and leave room in the outer budget for the complete operation.
Python example
import requests
endpoint = "https://YOUR_BROWSERLESS_HOST/screenshot"
params = {
"token": "YOUR_API_TOKEN",
"timeout": 90000, # Overall request budget, in milliseconds
}
payload = {
"url": "https://example.com/slow-page",
"options": {
"type": "png",
"gotoOptions": {
"waitUntil": "domcontentloaded",
"timeout": 30000,
},
"waitForSelector": {
"selector": "main h1",
"timeout": 20000,
},
},
}
response = requests.post(
endpoint,
params=params,
json=payload,
timeout=100, # Client socket budget in seconds; leave room beyond the API budget
)
response.raise_for_status()
with open("screenshot.png", "wb") as image_file:
image_file.write(response.content)
Node.js example
const endpoint = new URL("https://YOUR_BROWSERLESS_HOST/screenshot");
endpoint.searchParams.set("token", "YOUR_API_TOKEN");
endpoint.searchParams.set("timeout", "90000");
const payload = {
url: "https://example.com/slow-page",
options: {
type: "png",
gotoOptions: {
waitUntil: "domcontentloaded",
timeout: 30000,
},
waitForSelector: {
selector: "main h1",
timeout: 20000,
},
},
};
const response = await fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
signal: AbortSignal.timeout(100_000), // Client budget in milliseconds
});
if (!response.ok) {
const detail = await response.text();
throw new Error(`Browserless returned ${response.status}: ${detail}`);
}
const image = Buffer.from(await response.arrayBuffer());
await import("node:fs/promises").then(({ writeFile }) =>
writeFile("screenshot.png", image)
);
The client timeout is separate from Browserless’s request timeout. Set the client’s network deadline a little higher so it does not close the connection first. A client timeout does not extend the Browserless route or account’s allowed session duration.
4. Wait for the content you need
Choose a readiness signal that matches the page:
- Selector: Wait for a stable element that must exist in the captured result, such as the main heading or report container. A selector that is misspelled, conditional, or absent can cause a non-200 response.
- Function or event: Use a condition tied to the specific page state when a selector alone is not sufficient, if your route supports it.
- Fixed delay: Use
waitForTimeoutonly when you know a transition needs time and no better readiness signal is available. Keep it short because the delay consumes the overall budget whether the page is ready or not.
If the awaited selector or event is legitimately optional, decide whether a missing condition should fail the capture. Where supported, bestAttempt lets the request continue when an awaited event fails or times out. Treat that as a deliberate fallback: inspect the response and resulting image because proceeding may produce an incomplete screenshot. The [request configuration reference](https://docs.browserless.io/rest-apis/request-configuration) describes these wait behaviors.
5. Keep endpoint generations separate
Confirm the route and API generation before copying timeout syntax. This guide addresses the current REST POST /screenshot route. The older BaaS v1 screenshot interface and BrowserlessQL’s GraphQL screenshot mutation have distinct request shapes and options. In particular, do not copy the older BaaS v1 waitFor interface into a current REST request or assume BrowserlessQL’s screenshot-operation timeout is the REST request timeout. Follow the matching [REST screenshot guide](https://docs.browserless.io/rest-apis/screenshot), [BaaS v1 reference](https://docs.browserless.io/baas/v1/rest-apis/screenshot), or [BrowserQL screenshot schema](https://docs.browserless.io/browserql/screenshot).
6. Troubleshoot common timeout failures
| Symptom | Likely cause | What to change |
|---|---|---|
| The whole request ends at a consistent elapsed time. | The overall REST timeout or a client-side deadline is expiring. | Compare elapsed time with both budgets. Raise the budget that expires, keeping the client deadline slightly above the API timeout. |
| Navigation times out even though the page partly rendered. | waitUntil may be waiting for a later lifecycle event or network activity to stop. |
Use a less demanding supported condition, then wait for the content element that matters. |
| The response reports that a selector did not appear. | The selector is incorrect, conditional, delayed, or the page did not reach the expected state. | Check the selector against the page, confirm its state can occur, and tune its own timeout. Use bestAttempt only if proceeding without it is acceptable and supported. |
| A larger timeout has no effect. | You changed a different timeout layer, used an option from another API generation, or hit the route or plan ceiling. | Verify route and schema, determine which budget expires, and check the account’s session limit. |
| The client sees a network timeout or connection error. | The client or an intermediate connection deadline may be shorter than the Browserless request. | Set the client deadline above the API budget and record the actual HTTP status or transport error separately. |
| The call fails when using a very large timeout. | The requested duration may exceed the session or plan maximum. | Check the matching launch documentation and plan limit; increasing the request parameter does not override that ceiling. |
| The screenshot succeeds but misses late content. | Navigation completed, but the content was not ready at capture time. | Wait for a content-specific selector or function rather than relying on a generic lifecycle event alone. |
For a useful diagnostic record, log the route, redacted request parameters, non-secret body fields, start time, elapsed time, HTTP status, and response error text. Do not log API tokens, authorization headers, or sensitive page content.
7. Performance, reliability, and cost
Longer timeouts can reduce failures on genuinely slow pages, but they also keep requests open longer and can increase total elapsed time. A selector wait usually gives a more useful stopping condition than an arbitrary delay. Pick the earliest condition that means the screenshot will contain the required content.
Keep budgets finite and size them from observed request durations in your own application. Record which stage failed so you can tune navigation, readiness, or the client deadline independently. If you process multiple screenshots, handle timeout responses as individual failures and apply a bounded retry policy only when the failure may be transient; repeating a request with identical conditions will not fix a selector that can never appear.
Browserless documentation says the maximum session timeout depends on the plan. Check the applicable route and account limits before assigning very large budgets. The supplied documentation does not establish a universal cost per screenshot or guarantee that a longer timeout changes billing, so consult your plan terms for cost details.
Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns an image or PDF. Its clean-shot flow accepts cookie and consent banners like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each of those steps can be turned off. Bot checks and CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response reports the page verdict and billing status in headers. AI agents can use its MCP server tools to take screenshots, inspect page information, and capture PDFs.
See the ScreenshotNeo API documentation for configuration options. For a simple capture, use:
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, popups, and chat widgets are removed before the shot. Bot checks, blank pages, and failed loads are never billed. An MCP server lets AI agents take screenshots. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 screenshots.
Sign up free for 1,000 screenshots a month, with no card required.
FAQ
Should I always use networkidle0 for screenshots?
No. Use it when the page’s network actually becomes idle and that state is useful. Pages with ongoing requests may never satisfy it; a supported earlier lifecycle condition plus a content-specific wait can be a better fit.
Does a 90-second overall timeout mean navigation can take 90 seconds?
Not if a separate navigation timeout is shorter. Each configured stage has its own budget, and the overall request budget must cover the complete sequence.
Can I use bestAttempt for every selector wait?
Only when continuing without the awaited condition is acceptable and the endpoint supports the option. Otherwise, the missing content may make the screenshot useless.
Does raising the REST timeout override the Browserless session limit?
No. The session limit is a separate constraint, and the maximum depends on the plan and endpoint context.


