ScreenshotNeo

BlogHow-to

BrowserStack Screenshot API Example in Node.js for Indian Websites

Submit a website to BrowserStack’s Screenshots API from Node.js, poll for image URLs, and learn when India geolocation requires Automate instead.

By the ScreenshotNeo team4 October 202610 min read

To capture an Indian website with BrowserStack’s Screenshots API in Node.js, submit its URL and browser/OS configuration to the Screenshots API, authenticate with your BrowserStack username and access key, then poll the returned job until it contains screenshot image URLs. A URL associated with India does not make the capture originate from India: the reviewed Screenshots API parameters do not document an India geolocation option. For a test whose network traffic must originate in India, use BrowserStack Automate’s documented geolocation capability with country code IN, subject to Enterprise eligibility.

The Screenshots API is documented for Automate plans that include browsers. Check your account access and current API documentation before building a production workflow. BrowserStack Screenshots API documentation.

Choose the right kind of India capture

First decide what “Indian website” means for your task:

  • The site is hosted in India or serves Indian content: submit its public URL to the Screenshots API. This captures the page using the browser/OS options you choose, but does not establish that the request came from an Indian IP address.
  • The site must see an India-originating request: configure an Automate test with the documented geoLocation: "IN" capability. BrowserStack documents this geolocation feature as Enterprise-only. Do not add this capability to the Screenshots API payload and assume it is supported there.
  • The page varies by local time: timezone simulation is separate from IP geolocation. Automate documentation lists Kolkata as a timezone capability value; that alone does not route traffic through an Indian IP.

These approaches answer different questions. The Screenshots API is for producing images across browser configurations. Automate with geolocation is for running a browser test with traffic from a selected country.

What the Screenshots API workflow does

  1. Submit a POST request with a target URL and supported browser/OS selections.
  2. Authenticate using HTTP Basic authentication with your BrowserStack username and access key.
  3. Read the job identifier in the response.
  4. Poll the documented job endpoint until processing completes, then use the returned image_url values.

The API also documents callback delivery when you provide a public callback URL. Its options include browser and operating system selection, mobile device and orientation settings, screen resolution, image quality, local testing when a Local connection is configured, and wait_time. The request is asynchronous, so design for a period where the job is still running.

Node.js example: submit, poll, and print screenshot URLs

This runnable example uses Node.js built-in fetch and Buffer (Node.js 18 or later). It uses the documented REST flow and reads the endpoint paths from environment variables so you can set them to the exact submit and job endpoints shown in your current BrowserStack account documentation. BrowserStack’s API documentation can change; confirm the current paths, payload field names, and response shape before running it. The historic browser versions in old examples may no longer be supported, so retrieve current choices from the browser-list endpoint and replace the example configuration.

// save as screenshot.mjs
const username = process.env.BROWSERSTACK_USERNAME;
const accessKey = process.env.BROWSERSTACK_ACCESS_KEY;
const submitEndpoint = process.env.BROWSERSTACK_SCREENSHOTS_SUBMIT_ENDPOINT;
const jobEndpointTemplate = process.env.BROWSERSTACK_SCREENSHOTS_JOB_ENDPOINT_TEMPLATE;

if (!username || !accessKey || !submitEndpoint || !jobEndpointTemplate) {
  throw new Error(
    'Set BROWSERSTACK_USERNAME, BROWSERSTACK_ACCESS_KEY, ' +
    'BROWSERSTACK_SCREENSHOTS_SUBMIT_ENDPOINT, and ' +
    'BROWSERSTACK_SCREENSHOTS_JOB_ENDPOINT_TEMPLATE.'
  );
}

// Replace these example selections with browser/OS combinations currently
// supported by your account. The Screenshots API docs define the request fields.
const payload = {
  url: 'https://example.in',
  // Add documented browser/OS, resolution, wait_time, or other supported options.
};

const authorization = `Basic ${Buffer.from(`${username}:${accessKey}`).toString('base64')}`;

async function readJson(response) {
  const body = await response.text();
  if (!response.ok) {
    throw new Error(`HTTP ${response.status}: ${body}`);
  }
  try {
    return JSON.parse(body);
  } catch {
    throw new Error(`Expected JSON response, got: ${body.slice(0, 500)}`);
  }
}

const submitted = await fetch(submitEndpoint, {
  method: 'POST',
  headers: {
    Authorization: authorization,
    'Content-Type': 'application/json',
    Accept: 'application/json',
  },
  body: JSON.stringify(payload),
});
const job = await readJson(submitted);

// Adapt this field to the job identifier in the current documented response.
const jobId = job.job_id ?? job.id;
if (!jobId) {
  throw new Error(`No job identifier in response: ${JSON.stringify(job)}`);
}

const jobUrl = jobEndpointTemplate.replace('{job_id}', encodeURIComponent(jobId));
const maxAttempts = 60;
const intervalMs = 2000;

for (let attempt = 1; attempt <= maxAttempts; attempt++) {
  const response = await fetch(jobUrl, {
    headers: { Authorization: authorization, Accept: 'application/json' },
  });
  const result = await readJson(response);

  // Inspect the documented status field and adapt these values if needed.
  const status = String(result.status ?? '').toLowerCase();
  if (['done', 'completed', 'success', 'successful'].includes(status)) {
    const screenshots = result.screenshots ?? result.results ?? [];
    const imageUrls = screenshots
      .map((item) => item.image_url ?? item.imageUrl)
      .filter(Boolean);
    if (imageUrls.length === 0) {
      console.log('Job completed. Inspect the response for its documented image URL field:');
      console.log(JSON.stringify(result, null, 2));
    } else {
      console.log(imageUrls.join('\n'));
    }
    process.exit(0);
  }
  if (['error', 'failed', 'failure'].includes(status)) {
    throw new Error(`Screenshot job failed: ${JSON.stringify(result)}`);
  }
  await new Promise((resolve) => setTimeout(resolve, intervalMs));
}
throw new Error(`Timed out waiting for job ${jobId}; check its status endpoint or use a callback.`);

Run it after setting the four environment variables to credentials and endpoint paths from the current API docs:

export BROWSERSTACK_USERNAME='your-username'
export BROWSERSTACK_ACCESS_KEY='your-access-key'
export BROWSERSTACK_SCREENSHOTS_SUBMIT_ENDPOINT='the-current-submit-endpoint'
export BROWSERSTACK_SCREENSHOTS_JOB_ENDPOINT_TEMPLATE='the-current-job-endpoint/{job_id}'
node screenshot.mjs

The placeholders are deliberate: the research available for this article establishes the API’s asynchronous behavior and fields/options at a high level, but does not provide the current literal endpoint paths or a complete current request/response schema. Copy those exact values from BrowserStack’s live documentation rather than relying on an invented URL or stale sample.

cURL: submit a capture and inspect the response

Use the endpoint and JSON field names from the current Screenshots API documentation. curl --user sends HTTP Basic authentication; keep the access key in an environment variable rather than in source control.

curl --user "$BROWSERSTACK_USERNAME:$BROWSERSTACK_ACCESS_KEY" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json' \
  -X POST "$BROWSERSTACK_SCREENSHOTS_SUBMIT_ENDPOINT" \
  --data '{"url":"https://example.in"}'

Save the returned job identifier, then query the documented job endpoint with the same authentication. Repeat until the status indicates completion, and read the screenshot image URLs from the completed response.

Python: submit and poll with requests

The same asynchronous flow works from Python. Install requests with python -m pip install requests, set the environment variables, and adapt the endpoint and response fields to the current documentation.

import os
import time
import requests
from requests.auth import HTTPBasicAuth

username = os.environ["BROWSERSTACK_USERNAME"]
access_key = os.environ["BROWSERSTACK_ACCESS_KEY"]
submit_endpoint = os.environ["BROWSERSTACK_SCREENSHOTS_SUBMIT_ENDPOINT"]
job_template = os.environ["BROWSERSTACK_SCREENSHOTS_JOB_ENDPOINT_TEMPLATE"]
auth = HTTPBasicAuth(username, access_key)

payload = {
    "url": "https://example.in",
    # Add currently documented browser/OS and capture options here.
}

response = requests.post(
    submit_endpoint,
    json=payload,
    auth=auth,
    timeout=30,
)
response.raise_for_status()
job = response.json()
job_id = job.get("job_id") or job.get("id")
if not job_id:
    raise RuntimeError(f"No job identifier in response: {job}")

job_url = job_template.replace("{job_id}", requests.utils.quote(str(job_id), safe=""))
for _ in range(60):
    status_response = requests.get(job_url, auth=auth, timeout=30)
    status_response.raise_for_status()
    result = status_response.json()
    status = str(result.get("status", "")).lower()
    if status in {"done", "completed", "success", "successful"}:
        screenshots = result.get("screenshots") or result.get("results") or []
        urls = [item.get("image_url") or item.get("imageUrl") for item in screenshots]
        urls = [url for url in urls if url]
        print("\n".join(urls) if urls else result)
        break
    if status in {"error", "failed", "failure"}:
        raise RuntimeError(f"Screenshot job failed: {result}")
    time.sleep(2)
else:
    raise TimeoutError(f"Timed out waiting for job {job_id}")

Options to configure

Need What to configure Notes
Cross-browser coverage Browser and operating system selections Use combinations currently returned by BrowserStack’s browser-list endpoint; avoid hard-coding old versions.
Mobile browser screenshot Documented device and orientation options Check supported device names and whether the chosen configuration is available to your account.
Viewport and image detail Screen resolution and image quality Higher detail can increase image size and transfer time.
Wait for client-side rendering wait_time Use a value appropriate to the page; long waits add latency and do not fix a page that never becomes ready.
Private site under test Local testing with a configured Local connection Set up the Local connection before submitting the capture.
Automation callback Public callback URL Useful for avoiding repeated status polling; the callback must be reachable by BrowserStack.

For pages that vary by Indian visitor location, a country-level Automate geolocation capability is not the same as setting browser locale, timezone, or a page language. Determine which signal the site uses: source IP, browser timezone, cookies, or account state. Validate the actual behavior in an eligible Automate setup.

India geolocation with Automate

BrowserStack’s Automate capabilities use a country code such as IN for geolocation, and its documentation marks geolocation as Enterprise-only. This is for an Automate test configuration; the reviewed Screenshots API parameter list does not document the same option. Consult the [BrowserStack geolocation documentation](https://www.browserstack.com/docs/automate/selenium/ip-geolocation) and [Automate capabilities documentation](https://www.browserstack.com/docs/automate/selenium/capabilities) for current eligibility and syntax.

Do not treat timezone simulation as a substitute for location-based network routing. If a page branches on local time, configure the documented timezone capability such as Kolkata where eligible. If it branches on IP, use geolocation. If it branches on language or consent, configure those separately in the test.

Reliability, performance, and cost considerations

  • Plan for asynchronous completion: the job may still be running after submission. Poll with a bounded interval and deadline, or use the documented callback route.
  • Use a reasonable wait: a larger wait_time can help content that appears after scripts run, but increases end-to-end latency. It cannot guarantee success when the site is blocked, unavailable, or requires interaction.
  • Limit configuration fan-out: each requested browser/OS combination adds work and resulting images. Request only the combinations needed for the decision being made.
  • Handle transient errors: retry temporary network and server errors with capped backoff; do not blindly repeat a submission after an ambiguous timeout because that can create duplicate jobs.
  • Protect credentials and result URLs: keep the username and access key in environment secrets. Treat returned image URLs as potentially sensitive if they expose internal or pre-release pages.
  • Check plan eligibility: Screenshots API access is documented as part of Automate plans that include browsers, and India geolocation is documented as Enterprise-only. Confirm current entitlements and pricing directly with BrowserStack; the cited material does not establish a price for this workflow.

Common errors and fixes

Symptom Likely cause Fix
401 or 403 response Incorrect username/access key, malformed Basic authentication, or account lacks the relevant entitlement. Check secret values and account access. Keep the key out of committed code and logs.
Request rejected as invalid Unsupported or stale browser/OS value, wrong field name, or malformed payload. Use current browser-list data and the live Screenshots API schema.
Job remains queued or running Capture is still processing, configuration demand is high, or the target site is slow. Continue bounded polling or use a callback. Set an overall deadline and inspect the job response for state details.
Completed job has no image URL Code is reading a stale response field or the job did not complete successfully. Print and inspect the full documented response, then use the current image URL field and per-configuration status.
Page shows non-Indian content The capture URL does not imply an India-originating IP; Screenshots API location support was not established in the reviewed docs. Use eligible Automate geolocation with IN, and verify the page’s IP-based behavior.
Page uses the wrong local time Timezone differs from the page’s expected context. Configure the documented timezone separately; timezone does not change the source IP.
Local or intranet URL cannot load No BrowserStack Local connection is configured or the tunnel is unavailable. Start and verify Local before requesting a screenshot; use the documented local-testing option.
Polling times out Deadline is too short, polling endpoint is wrong, or job is stalled/failed. Verify the job ID and endpoint, inspect status payload, and use callback delivery for long-running flows.

Or skip the browser setup

If you need a screenshot API call rather than a BrowserStack browser matrix, ScreenshotNeo accepts a URL in one GET request and returns an image or PDF. Its API also has an MCP server for AI agents.

See the ScreenshotNeo API documentation for options and response details.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.in -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://example.in"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.in' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
  • Cookie and consent banners, newsletter popups, and chat widgets are removed before capture; each cleanup step can be turned off.
  • Bot checks, blank pages, failed loads, timeouts, and cache hits are never billed; response headers identify the page verdict and billing status.
  • An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf.
  • The free plan includes 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots.

Sign up for 1,000 free ScreenshotNeo screenshots a month, with no card.

FAQ

Does an Indian domain automatically use an Indian IP during capture?

No. The domain or URL does not establish the capture’s network origin. Use an eligible Automate geolocation setup when the test must originate in India.

Can I use the Screenshots API without polling?

The API documentation describes callback delivery as an alternative when you provide a public callback URL.

Should I use the npm package named browserstack?

Its npm page documents screenshot-client methods, but the research for this guide did not confirm current maintenance or compatibility. Direct REST calls make the authentication and asynchronous job flow explicit; verify package status and API compatibility before depending on a wrapper.

Is timezone Kolkata enough to test India-specific behavior?

Only if the page behavior depends on timezone. It does not make the request originate from an Indian IP address.