Timing & Delay
Not every page is ready the instant it loads. Single-page applications built with React, Vue, or Angular render their content with JavaScript after the initial HTML arrives. Images may lazy-load as you scroll. Charts animate into view. Data gets fetched from APIs. If you take the screenshot too early, you end up with blank areas, loading spinners, or half-rendered content.
The API gives you three tools to control exactly when the screenshot is taken: delay, timeout, and wait_for_selector.
Delay
The simplest timing control. After the page finishes loading, the API waits the specified number of seconds before taking the screenshot.
| Parameter | Type | Default | Description |
|---|---|---|---|
delay | integer | 0 | Wait N seconds after page load before capturing. Range: 0–10. |
A delay is useful when you know the page needs a moment to finish rendering — maybe a chart animation takes a second, or fonts need time to load, or a hero image fades in. It's a simple, reliable approach when you don't need precise control.
Example: Wait 2 seconds for animations
curl -X POST https://api.screenshotrun.com/v1/screenshots \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"delay": 2
}'
Timeout
The timeout sets the maximum time the API will wait for the page to load. If the page doesn't finish loading within this time, the request fails with a TIMEOUT error.
| Parameter | Type | Default | Description |
|---|---|---|---|
timeout | integer | 30 | Max seconds to wait for page load. Range: 5–60. |
The default of 30 seconds works for most pages. Increase it for heavy pages — data dashboards, SPAs that fetch large datasets, or pages behind slow servers. Decrease it for simple pages when you want faster failure on broken URLs.
Example: Extended timeout for a heavy dashboard
curl -X POST https://api.screenshotrun.com/v1/screenshots \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/analytics",
"timeout": 60,
"delay": 3
}'
Wait for selector
Sometimes you don't know exactly how long the page needs — you just know which element should appear before the screenshot is taken. The wait_for_selector parameter waits until a specific CSS selector appears in the DOM.
| Parameter | Type | Default | Description |
|---|---|---|---|
wait_for_selector | string | — | CSS selector to wait for before capturing. Waits up to 10 seconds. Max 500 characters. |
This is the most precise timing tool. Instead of guessing a delay, you tell the API: "wait until this element exists, then take the screenshot." It's perfect for SPAs where content loads asynchronously.
How to find the right selector
Open the page in your browser, wait for it to fully load, then right-click the element you're waiting for and choose "Inspect." Look for a unique class, ID, or attribute. You can test it in the browser console: document.querySelector('.your-selector') — if it returns the element, you have the right selector.
Example: Wait for a chart to render
const response = await fetch('https://api.screenshotrun.com/v1/screenshots', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://example.com/dashboard',
wait_for_selector: '.chart-container canvas',
}),
});
const { data } = await response.json();
console.log(data.links.image);
import requests
response = requests.post(
"https://api.screenshotrun.com/v1/screenshots",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={
"url": "https://example.com/dashboard",
"wait_for_selector": ".chart-container canvas",
},
)
data = response.json()["data"]
print(data["links"]["image"])
$response = Http::withToken('YOUR_API_KEY')
->post('https://api.screenshotrun.com/v1/screenshots', [
'url' => 'https://example.com/dashboard',
'wait_for_selector' => '.chart-container canvas',
]);
$data = $response->json('data');
curl -X POST https://api.screenshotrun.com/v1/screenshots \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/dashboard",
"wait_for_selector": ".chart-container canvas"
}'
require "net/http"
require "json"
uri = URI("https://api.screenshotrun.com/v1/screenshots")
http = Net::HTTP.new(uri.host, uri.port)
http.use_ssl = true
request = Net::HTTP::Post.new(uri)
request["Authorization"] = "Bearer YOUR_API_KEY"
request["Content-Type"] = "application/json"
request.body = {
url: "https://example.com/dashboard",
wait_for_selector: ".chart-container canvas",
}.to_json
response = http.request(request)
data = JSON.parse(response.body)["data"]
body, _ := json.Marshal(map[string]any{
"url": "https://example.com/dashboard",
"wait_for_selector": ".chart-container canvas",
})
req, _ := http.NewRequest("POST",
"https://api.screenshotrun.com/v1/screenshots",
bytes.NewBuffer(body))
req.Header.Set("Authorization", "Bearer YOUR_API_KEY")
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
var result map[string]any
json.NewDecoder(resp.Body).Decode(&result)
fmt.Println(result["data"])
var client = HttpClient.newHttpClient();
var body = """
{
"url": "https://example.com/dashboard",
"wait_for_selector": ".chart-container canvas"
}
""";
var request = HttpRequest.newBuilder()
.uri(URI.create("https://api.screenshotrun.com/v1/screenshots"))
.header("Authorization", "Bearer YOUR_API_KEY")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body))
.build();
var response = client.send(
request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body());
Example: Wait for React app content
curl -X POST https://api.screenshotrun.com/v1/screenshots \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/app",
"wait_for_selector": "#root .main-content"
}'
Combining timing parameters
These parameters work together in a specific order: the page loads (within the timeout limit), then wait_for_selector waits for the element, and finally delay adds extra wait time. This lets you handle complex scenarios.
Example: Chart with animation
Wait for the chart canvas to appear, then wait an extra second for the animation to finish.
curl -X POST https://api.screenshotrun.com/v1/screenshots \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/dashboard",
"wait_for_selector": ".chart-container canvas",
"delay": 1,
"timeout": 45
}'
1. Page starts loading
2. timeout — max wait for the page to load
3. wait_for_selector — wait for the element (up to 10 seconds)
4. delay — additional wait after everything is ready
5. Screenshot is taken
Lazy-loaded images
Many modern websites use a technique called lazy loading: images below the visible area aren't loaded until the user scrolls down to them. The browser uses Intersection Observer to detect when an image enters the viewport and only then starts fetching it.
This creates a problem for full-page screenshots. The API captures the entire page at once without actually scrolling, so images below the initial viewport never get triggered — they stay as blank grey placeholders.
The fix: scroll before capture
The most reliable solution is to inject a JavaScript snippet that scrolls through the entire page before the screenshot is taken. This triggers all lazy-load observers and gives images time to load. The snippet then scrolls back to the top so the screenshot starts from the beginning.
curl -X POST https://api.screenshotrun.com/v1/screenshots/capture \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"full_page": true,
"delay": 2,
"js": "await new Promise(resolve => { let total = 0; const step = 500; const timer = setInterval(() => { window.scrollBy(0, step); total += step; if (total >= document.body.scrollHeight) { clearInterval(timer); window.scrollTo(0, 0); resolve(); } }, 100); });"
}' \
-o screenshot.png
How the scroll snippet works
The script scrolls down in 500-pixel steps every 100 milliseconds. Each scroll step triggers Intersection Observers for any images that come into view. Once it reaches the bottom of the page, it scrolls back to the top and resolves the promise, allowing the screenshot to proceed.
Combine this with delay to give images extra time to finish loading after the scroll completes. A delay of 2–3 seconds usually covers even slower connections.
If some images still appear blank after scrolling, try increasing the interval between scroll steps (change 100 to 200 or 300) or add a higher delay value. Some pages use libraries that need more time between scroll events to start fetching images.
Troubleshooting
| Problem | Likely cause | Solution |
|---|---|---|
| Screenshot shows a loading spinner | Page not fully rendered | Increase delay or use wait_for_selector to wait for the main content |
| Screenshot is completely blank | Page needs more time, or blocks headless browsers | Increase timeout, or try stealth mode |
| Lazy images missing in full-page capture | Intersection Observer not triggered | Use the JavaScript scroll snippet above |
| Some dynamic content not appearing | Content loaded via API after initial render | Use wait_for_selector targeting the loaded content's container |