Most reconnaissance tools were built for a web that served static HTML. You'd curl a URL, grep through the response, and pull out forms, links, and meta tags in a few seconds. That worked in 2015. Today, a React SPA returns an empty <div id="root"> until JavaScript runs, a Shopify store loads its product grid via client-side fetch calls, and a banking portal renders its login form inside a shadow DOM component. Curl sees none of it.
The Page Analysis API loads the target URL in a real Chromium instance, executes all JavaScript, waits for the page to stabilize, and returns structured JSON with every element on the rendered page. Forms with their hidden fields. All clickable elements with href targets. Navigation structure. Meta tags. Third-party scripts. One POST request gives you the full rendered surface of any URL — the same page a real visitor would see.
curl -X POST https://api.screenshotrun.com/v1/analyze \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://target.example.com",
"include": ["interactive", "forms", "headings", "images", "navigation"],
"stealth": true
}'
What rendered-page reconnaissance captures
Static analysis tools like curl, wget, or even Burp Spider work on the raw HTTP response. They miss everything that JavaScript adds after page load. And on modern sites, that's most of the page.
Rendered-page reconnaissance captures what's actually there after the browser finishes its work:
- Forms — every
<form>on the page, including login portals, search bars, contact forms, file upload fields, and hidden inputs that static parsers never see. The API returns field names, types, placeholder text, required status, and form action URLs. (Extract Forms API) - Interactive elements — buttons, links, dropdowns, tabs, modals, and any element with click handlers. Each one comes with its visible text, href, ARIA role, and pixel-precise bounding rectangle. (Extract Clickable Elements API)
- Navigation structure — how the site organizes its pages. Header menus, footer links, sidebar navigation, breadcrumbs. Useful for mapping site architecture before deeper crawling.
- Third-party resources — tracking scripts, analytics pixels, ad networks, CDN references, chat widgets. Everything loaded into the page after rendering.
- Technology markers — framework-specific class names (React:
data-reactroot, Angular:ng-version, Vue:data-v-), CMS meta tags (WordPress:wp-content, Shopify:Shopify.theme), generator meta tags. - Hidden elements — set
visible_only: falseto include elements withdisplay: none, off-screen positioning, or zero opacity. Hidden forms, honeypot fields, debug panels.
Why do static recon tools miss what matters?
A curl request to a modern SPA returns the shell: a few divs, a script bundle URL, and nothing else. The actual page content — the login form, the navigation, the product listings — gets injected by JavaScript after the bundle executes. Tools like recon-ng, reconftw, and EyeWitness either skip JavaScript entirely or run a limited headless browser that times out before complex SPAs finish rendering.
Even Burp Suite's built-in crawler, which does execute some JavaScript, struggles with client-side routing, lazy-loaded components, and single-page applications that never trigger a traditional page load. So you end up with partial results and manual cleanup.
The gap is structural. These tools were designed for server-rendered HTML where every URL returns a complete document. That model doesn't match how most websites work anymore. When 70% of new sites ship as SPAs or use client-side hydration, a reconnaissance workflow that can't render JavaScript is missing the majority of the attack surface.
Mapping the attack surface of a web application
In a typical pre-engagement or bug bounty workflow, the first step is surface mapping: find every form, every input, every endpoint the application exposes. Pentesters usually piece this together from five or six different tools — a subdomain enumerator, a web spider, a screenshot tool, a technology detector, a form scanner. Each one handles one slice of the picture.
With the analyze endpoint, one API call returns the full element inventory of a rendered page. Here's a Python script that maps the interactive surface of a target:
import requests
API_KEY = "YOUR_API_KEY"
def recon_page(url):
resp = requests.post(
"https://api.screenshotrun.com/v1/analyze",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"url": url,
"include": ["forms", "interactive", "navigation", "headings"],
"visible_only": False,
"stealth": True,
},
timeout=60,
)
return resp.json()["result"]
result = recon_page("https://target.example.com")
# List all forms with their fields
for i, form in enumerate(result.get("forms", [])):
action = form.get("action", "(no action)")
method = form.get("method", "GET")
print(f"\nForm {i+1}: {method} → {action}")
for field in form.get("fields", []):
hidden = " [HIDDEN]" if field.get("type") == "hidden" else ""
print(f" {field['name']}: {field['type']}{hidden}")
# External links (potential third-party integrations)
external = [e for e in result.get("interactive", [])
if e.get("href", "").startswith("http")
and "target.example.com" not in e.get("href", "")]
print(f"\n{len(external)} external links found")
for link in external[:10]:
print(f" → {link['href']} ({link.get('text', 'no text')})")
That runs in about three seconds. You get every form (including hidden fields that might accept SQL or XSS payloads), every outbound link (potential third-party dependencies to investigate), and the full navigation structure for planning deeper crawling.
Technology fingerprinting from the rendered DOM
BuiltWith and Wappalyzer identify technologies by pattern-matching against HTTP headers and static HTML. They're good at catching WordPress, jQuery, and Google Analytics. But they miss frameworks that only manifest after JavaScript runs — React components that add data-reactroot to the DOM, Next.js pages that inject __NEXT_DATA__ scripts, Nuxt applications with their __nuxt divs.
The DOM-to-JSON API gives you the full rendered DOM tree as structured data. Parse it for framework markers, inline scripts, imported modules, and dynamically loaded third-party resources that static analysis never touches.
# Technology fingerprinting from rendered page
import re
result = recon_page("https://target.example.com")
# Check for framework indicators in interactive elements
all_classes = []
for el in result.get("interactive", []):
all_classes.extend(el.get("classes", []))
tech_signals = {
"React": any("react" in c.lower() for c in all_classes),
"Angular": any(c.startswith("ng-") or c.startswith("_ng") for c in all_classes),
"Vue": any(c.startswith("v-") or "data-v-" in c for c in all_classes),
"Tailwind": any(re.match(r"^(flex|grid|p-|m-|text-|bg-|w-|h-)", c) for c in all_classes),
"Bootstrap": any("btn-" in c or "col-" in c or "navbar" in c for c in all_classes),
}
for tech, detected in tech_signals.items():
if detected:
print(f" Detected: {tech}")
BuiltWith charges $295-995/month for their API. Wappalyzer's API plans start at $250/month. Both work from cached crawl data, not live renders. You get stale results and no DOM-level detail. The analyze endpoint gives you the current rendered state of any page, on demand, for a fraction of the cost.
Geo-located reconnaissance
Websites serve different content based on visitor location. A banking site might show different products in the EU vs. the US. An e-commerce store might restrict certain pages by country. Phishing pages sometimes target specific regions and block traffic from others.
The geo-screenshot parameter lets you run reconnaissance from specific locations. Pass a country code, and the page loads through a proxy in that region. You see what a local visitor sees — including geo-blocked content, localized forms, and region-specific tracking scripts.
# Compare page structure across geolocations
regions = ["US", "DE", "JP", "BR"]
for region in regions:
resp = requests.post(
"https://api.screenshotrun.com/v1/analyze",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"url": "https://target.example.com",
"include": ["forms", "interactive"],
"geolocation": region,
"stealth": True,
},
timeout=60,
)
data = resp.json()["result"]
forms = len(data.get("forms", []))
links = len(data.get("interactive", []))
print(f"{region}: {forms} forms, {links} interactive elements")
If the form count changes between regions, something is being geo-restricted or geo-targeted. That's useful for both offensive assessments (finding region-specific attack surfaces) and defensive monitoring (verifying that your own geo-restrictions work correctly).
Stealth mode for protected targets
Many sites detect headless browsers and serve different content — or block the request entirely. Bot detection services like Cloudflare, Akamai, and DataDome fingerprint browser characteristics: JavaScript execution patterns, WebGL rendering, navigator properties, timing behaviors.
The stealth parameter applies evasion techniques so the target sees a normal browser session. This matters for reconnaissance because a bot-blocked page gives you false data. You need to see what a real user sees. Without stealth, your recon results might be a Cloudflare challenge page instead of the actual application.
Batch reconnaissance across multiple targets
Subdomain enumeration is standard practice during recon. Once you've got a list of live subdomains — 50, 200, maybe a thousand — you need to quickly assess what each one hosts. Which ones have login forms? Which run WordPress? Which have admin panels?
The API handles this programmatically. Loop through your subdomain list, analyze each one, and build a structured inventory:
import json
subdomains = [
"https://app.target.example.com",
"https://admin.target.example.com",
"https://api.target.example.com",
"https://staging.target.example.com",
"https://blog.target.example.com",
]
inventory = []
for url in subdomains:
try:
data = recon_page(url)
entry = {
"url": url,
"forms": len(data.get("forms", [])),
"has_login": any(
f.get("method", "").upper() == "POST"
and any(fld.get("type") == "password" for fld in f.get("fields", []))
for f in data.get("forms", [])
),
"interactive_count": len(data.get("interactive", [])),
"headings": [h["text"] for h in data.get("headings", []) if h.get("level") == 1],
}
inventory.append(entry)
status = "🔑 LOGIN" if entry["has_login"] else f"{entry['forms']} forms"
print(f" {url}: {status}, {entry['interactive_count']} interactive elements")
except Exception as e:
print(f" {url}: FAILED ({e})")
# Save structured inventory
with open("recon_inventory.json", "w") as f:
json.dump(inventory, f, indent=2)
Five subdomains take about fifteen seconds. Fifty take a couple of minutes. You get a JSON inventory showing which subdomains have login portals, how many forms each one exposes, and what the H1 says — enough to prioritize which targets to investigate further.
Defensive reconnaissance: monitoring your own surface
Reconnaissance isn't only for offensive testing. Security teams use the same techniques to monitor their own web properties. Did a developer accidentally expose a debug form? Did a CMS plugin inject an unexpected iframe? Did a third-party script get compromised and start loading from a different domain?
Schedule periodic analyze calls against your production pages via webhooks. Compare the current element inventory against a known-good baseline. If new forms appear, external scripts change, or navigation links redirect somewhere unexpected, fire an alert. The defacement monitoring use case covers the visual side of this; structural reconnaissance catches changes that don't affect appearance but do affect security.
How this compares to existing recon tools
Shodan and Censys scan network infrastructure — open ports, SSL certificates, server banners. They tell you what's running at the network level but nothing about what the web application shows to users. SecurityTrails maps DNS history and domain relationships. BuiltWith and Wappalyzer detect technology stacks from cached data, often days or weeks old.
But none of these tools render the page. None of them extract forms, map interactive elements, or return the DOM structure of a live web application. They're complementary — use Shodan for infrastructure, SecurityTrails for DNS, and our analyze endpoint for the rendered page layer. Together they cover the full reconnaissance surface.
Open-source alternatives like reconftw, reNgine, and EyeWitness combine multiple tools into automated pipelines. They're powerful but require server infrastructure, ongoing maintenance, and significant setup time. reconftw's full scan chain can take hours per target. The analyze endpoint returns results in seconds, needs zero infrastructure, and integrates into any language or workflow with a single HTTP call.
Compliance and vendor security assessments
PCI DSS 6.4.3 requires an inventory of all scripts loaded on payment pages. SOC 2 audits ask for evidence that security controls are in place. Vendor risk assessments need documentation of a third party's web application surface.
The analyze response provides exactly this: a structured, timestamped inventory of every element, script, and form on a rendered page. Export it as evidence for compliance audits. Run it weekly against vendor portals to detect changes. Feed the markdown extraction into reports. The data is JSON — it goes wherever your compliance workflow needs it.
API parameters for reconnaissance
include— element categories to extract:forms,interactive,headings,images,navigation,text. Pick what your recon workflow needs.visible_only— set tofalseto catch hidden forms, honeypot fields, and off-screen elements.stealth— bypass bot detection to see the real page, not the challenge screen.geolocation— run recon from a specific country to detect geo-restrictions and localized content.block_cookies,block_ads,block_chats— strip overlays that obscure the actual application interface.with_screenshot— capture a visual snapshot alongside the JSON for reporting and evidence.delay— wait 2-3 seconds after load for SPAs that take time to hydrate.scope— limit analysis to a specific page region using a CSS selector.
Full documentation on the Page Analysis docs page. All screenshot parameters apply: CSS/JS injection, custom headers, proxy support, timezone, and cookies for authenticated session analysis.
Why use ScreenshotRun for website reconnaissance
Traditional recon tools give you either raw HTTP responses or network-level data. Neither shows you what the page actually renders. We render URLs in a real browser, execute all JavaScript, and return the full element inventory as structured JSON. Forms with hidden fields, interactive elements with their targets, navigation maps, technology markers — everything a static parser would miss.
And you don't set up infrastructure, maintain headless browser instances, or chain together five separate tools. One API call per URL, results in seconds, JSON you can pipe into any workflow. The stealth mode handles bot detection, geolocation handles regional variations, and visible_only: false catches what's deliberately hidden.
Screenshots, page analysis, markdown extraction, and DOM-to-JSON all run on the same rendered page. One API key covers the full reconnaissance workflow — visual capture for reporting, structured data for automation, clean text for AI analysis. Twenty free requests per month to test against your own targets.