The Page Analysis API loads a URL in a real browser, renders all JavaScript, and returns a structured JSON map of every element on the page. Headings, buttons, links, forms, images, navigation blocks. Each one comes back with its bounding rectangle in pixels: x, y, width, height. One POST request to /v1/analyze, no selectors to write, no browser infrastructure to manage.
curl -X POST https://api.screenshotrun.com/v1/analyze \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"include": ["headings", "interactive", "forms"]
}'
The response groups elements into six categories: headings (h1 through h6), interactive (buttons, links, inputs, anything clickable or focusable), forms (with action URL, method, and field count), images (src, alt text, natural dimensions), navigation (links inside <nav> elements), and text (paragraphs and content blocks). Every element includes a rect array — four numbers that pin it to an exact spot on the rendered page.
Why bounding rectangles change everything
Most tools that extract page data return text. Some return HTML or markdown. A few return structured JSON. But almost none tell you where each element sits on the rendered page. Firecrawl gives you clean markdown and LLM-extracted fields — no coordinates. Jina Reader converts URLs to text for RAG pipelines — no layout data. Even Cloudflare's Browser Rendering /scrape requires you to specify selectors upfront; it won't auto-discover every element on the page.
Coordinates matter when position matters. An AI agent deciding where to click needs pixel positions, not CSS selectors that might match three elements. A QA pipeline verifying that the "Buy Now" button sits above the fold needs its y-coordinate, not its innerHTML. A competitive intelligence tool tracking where CTAs sit across a rival's homepage needs spatial data that text alone can't provide.
{
"tag": "a",
"text": "Start Free Trial",
"href": "/register",
"role": "button",
"rect": [920, 24, 160, 44]
}
That rect tells you the element starts at 920px from the left edge, 24px from the top, spans 160px wide and 44px tall. Enough to draw an overlay, feed a vision model, or decide if the CTA is visible without scrolling.
What the API actually returns
Every response has two top-level objects: meta with timing and usage data, and result with the page analysis.
{
"meta": {
"status": 200,
"took": 2.4,
"tokens": 3200,
"bytes": 12800,
"requests": 1,
"screenshot_url": null,
"trial": false
},
"result": {
"url": "https://example.com",
"page": {
"title": "Example Domain",
"description": "Illustrative examples in documents.",
"language": "en",
"viewport": { "width": 1280, "height": 800 }
},
"headings": [
{ "level": 1, "text": "Example Domain", "rect": [200, 80, 880, 48] }
],
"interactive": [
{ "tag": "a", "text": "More information...", "href": "https://www.iana.org/domains/example", "rect": [200, 160, 180, 20] }
],
"forms": [],
"images": [],
"text": [
{ "text": "This domain is for use in illustrative examples in documents.", "rect": [200, 120, 880, 20] },
{ "text": "You may use this domain in literature without prior coordination.", "rect": [200, 148, 880, 20] }
],
"navigation": [],
"stats": {
"headings": 1,
"interactive": 1,
"forms": 0,
"images": 0,
"navigation": 0,
"text": 2
}
}
}
The meta.tokens field is there for LLM context budgeting. If you're feeding this into Claude or GPT-4, you know before parsing that this response will consume roughly 3,200 tokens of your context window. The stats object gives you counts without iterating through arrays — useful for quick filtering in pipelines that process thousands of URLs.
Six element categories
Headings return every h1 through h6 with its level, text, and position. Run this across a site and you have a heading hierarchy audit in JSON — without parsing HTML, writing regex, or fixing broken selectors after layout changes.
Interactive elements cover buttons, links, inputs, selects, textareas, and anything with role="button" or tabindex. Each includes the tag name, visible text, href (for links), type (for inputs), ARIA role, and bounding rectangle. This is the category AI agents care about most: it answers "what can I click, and where is it?"
Forms give you form-level data: action URL, HTTP method, field count, and position. If you need the individual fields inside a form, the interactive category already has them — inputs, selects, and textareas appear there with their form context.
Images include the src URL, alt text (empty string if missing — that's your accessibility flag), natural width and height, and position. Good for finding broken images, auditing alt text, and building visual datasets.
Navigation extracts links specifically from <nav> elements. Most sites wrap their primary menu in a <nav> tag, so this gives you the site's navigation structure in one array — useful for defacement monitoring (navigation suddenly changes) and verifying sitemaps.
Text blocks return paragraphs and content sections. Less structured than the other categories, but they complete the picture when you need a full page inventory.
Filtering: request only what you need
The include parameter lets you pick which categories to return. An AI agent that only needs clickable elements sends "include": ["interactive"] and gets back a response that's 80% smaller than the full analysis. A QA check that only cares about heading hierarchy sends "include": ["headings"]. This cuts response size and processing time — the renderer skips extracting categories you don't need.
// Node.js — get only forms and interactive elements
const response = await fetch("https://api.screenshotrun.com/v1/analyze", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SCREENSHOTRUN_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: "https://example.com/checkout",
include: ["forms", "interactive"],
visible_only: true,
}),
});
const data = await response.json();
if (!response.ok) {
console.error(`Error ${data.error?.code}: ${data.error?.message}`);
process.exit(1);
}
console.log(`${data.result.stats.forms} forms, ${data.result.stats.interactive} interactive elements`);
The visible_only parameter (default: true) filters to elements visible in the viewport. Set it to false if you need hidden elements too — off-screen navigation drawers, collapsed accordions, elements behind modals. Worth enabling for security audits: hidden forms or invisible inputs on a page often signal phishing.
Scope: analyze a section, not the whole page
Pass a CSS selector as the scope parameter to limit analysis to one container. The renderer finds the matching element and only extracts children inside it. Header, footer, sidebar content gets excluded.
{
"url": "https://example.com/pricing",
"scope": "#pricing-section",
"include": ["headings", "interactive"]
}
This returns only headings and buttons inside the pricing section. Scoping cuts noise and reduces response size when you know exactly which part of a page matters. It's particularly useful for e-commerce monitoring — scope to the product card container instead of analyzing the entire marketplace page.
Screenshot + analysis in one request
Set with_screenshot: true and the API renders a viewport screenshot alongside the structural analysis. The screenshot URL comes back in meta.screenshot_url. This costs one extra API request (2 total), but saves you a separate call to the screenshot endpoint.
import requests
response = requests.post(
"https://api.screenshotrun.com/v1/analyze",
headers={"Authorization": f"Bearer {API_KEY}"},
json={
"url": "https://competitor.com",
"with_screenshot": True,
"include": ["headings", "interactive", "images"],
"block_cookies": True,
"block_ads": True,
},
timeout=60,
)
data = response.json()
screenshot = data["meta"]["screenshot_url"]
elements = data["result"]["interactive"]
# Now you have both: a visual capture and a structured element map
for el in elements:
if el["tag"] == "a" and "pricing" in el.get("text", "").lower():
print(f"Pricing link at position {el['rect']}")
No other API currently combines a screenshot and structured element data in a single call. Firecrawl can take screenshots, but doesn't return element maps. PageBolt's /inspect returns element data, but screenshots are a separate endpoint. Here you get the visual context and the structured data together — useful when you need to show a human what the page looked like while your code processes the element positions.
Page Analysis for AI agents
AI agents built with LangChain, CrewAI, or Claude MCP need to understand web pages before they can interact with them. Today's options are limited. Raw HTML of a moderately complex page runs 50,000 to 200,000 tokens. That's most of an LLM's context window consumed before the agent even starts reasoning. Accessibility trees are smaller but lose spatial information. Screenshot-only approaches lose structural data entirely and cost extra inference tokens per vision call.
The analyze endpoint sits between these extremes. A typical page analysis returns 2,000 to 5,000 tokens of structured JSON. Two orders of magnitude smaller than raw HTML, and it preserves both structure and position. The agent knows there's a login form at coordinates [120, 300, 400, 250] with two text inputs and a submit button, without parsing a single line of HTML.
If your agent uses the ScreenshotRun MCP Server, the analyze_page tool wraps this endpoint. Claude calls it directly, gets structured JSON, and reasons about page layout without any custom code on your side.
Page Analysis vs. web scraping
Web scraping tools (Firecrawl, Jina Reader, Diffbot, SerpApi) pull content from pages: article text, product data, metadata. They answer "what does this page say?" Page Analysis pulls structure: elements, positions, categories, relationships. It answers "what is this page made of, and where is everything?"
They're complementary, not competing. A pipeline that monitors competitor landing pages might use Firecrawl to extract the copy and ScreenshotRun's analyze endpoint to track where CTAs sit, how forms change, and what the navigation looks like. Different tools for different questions.
For AI agents the difference is even sharper. An API that extracts text tells an agent what a page says. The analyze endpoint tells it what a page has: buttons to click, forms to fill, links to follow, and exactly where each one sits on screen.
All screenshot features included
The analyze endpoint inherits every rendering option from the screenshot API. Stealth mode bypasses bot defenses on sites that block headless browsers. Geolocation renders the page as seen from a specific country. Dark mode emulates prefers-color-scheme: dark. Cookie banner blocking, ad removal, and chat widget suppression clean up the page before analysis so you get the real content, not the overlay clutter.
The full list of rendering parameters (width, height, device, delay, timeout, hide_selectors, wait_for_selector, user_agent, proxy, timezone, headers, cookies) is in the API documentation. Every option the screenshot endpoint supports works here too.
You can also inject custom CSS or JavaScript before analysis runs. Test how a page looks with modified styles. Trigger a dropdown before capturing its contents. Dismiss a modal programmatically. The analysis runs after your injected code executes, so the results reflect the modified page state.
Pricing and limits
Page Analysis is a Pro feature with trial access on every plan. Free and Starter accounts get 20 analysis runs per month — enough to test your integration and validate the use case. Pro, Growth, and Business plans get unlimited analysis runs. Each analysis consumes 1 API request from your monthly quota (2 if you enable with_screenshot).
The meta.left field in every trial response tells you how many runs remain this month. When the trial limit runs out, the API returns a 402 status with a clear error message. No silent failures, no degraded responses.
Getting started
The full parameter reference, response schema, and code examples in four languages are in the Page Analysis documentation. The Playground lets you run analyses interactively — enter a URL, pick categories, and see the structured results with a visual element map. No code needed to start exploring.
If you're building an AI agent, the MCP Server integration gives Claude direct access to the analyze_page tool. For custom integrations, the Node.js and Python guides show the full request/response cycle with error handling.
FAQ#
A structured JSON object with six element categories: headings (h1–h6), interactive elements (buttons, links, inputs), forms (with action URL, method, field count), images (src, alt text, dimensions), navigation (links inside nav elements), and text blocks. Every element includes a bounding rectangle array [x, y, width, height] showing its exact pixel position on the rendered page.
Web scraping tools like Firecrawl or Jina Reader extract page content — article text, product data, metadata. Page Analysis extracts page structure — every element, its category, and its pixel position. Scraping answers "what does this page say?" while analysis answers "what is this page made of, and where is everything?"
Yes. Set with_screenshot to true in your request. The API renders a viewport screenshot alongside the structural analysis and returns the screenshot URL in meta.screenshot_url. This costs 2 API requests total instead of 1.
A typical response is 2,000 to 5,000 tokens of structured JSON — two orders of magnitude smaller than raw HTML. The meta.tokens field in every response tells you the exact token count before you parse the data, so you can budget your LLM context window.
Yes, in two ways. The include parameter lets you request specific element categories (e.g. only headings and interactive elements). The scope parameter accepts a CSS selector to limit analysis to one container on the page — everything outside that container is excluded.
Free and Starter accounts get 20 analysis runs per month as a trial. Pro, Growth, and Business plans get unlimited analysis runs. Each analysis consumes 1 API request from your monthly quota (2 if you enable with_screenshot).