# Quality Website Screenshot API (`inn_corp/quality-website-screenshot-api`) Actor

Full-page or viewport screenshots (PNG/JPEG/WebP/PDF) from a real headless Chrome browser, with a wait-for-selector option for JS-heavy pages.

- **URL**: https://apify.com/inn\_corp/quality-website-screenshot-api.md
- **Developed by:** [Inn Corp](https://apify.com/inn_corp) (community)
- **Categories:** AI, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 screenshot captureds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Quality Website Screenshot API

Give it URLs, get back full-page or viewport screenshots from a real
headless Chrome browser, not a static-image proxy: PNG, JPEG, WebP, or PDF,
with a `waitForSelector` option for pages whose real content renders after a
JavaScript call. Every dimension in the output is decoded straight out of
the captured file itself, not estimated beforehand.

### Why this exists in a crowded category

"Screenshot API" is a crowded search term on the Apify Store, 1,700+ listed
results, and this build does not pretend otherwise. The free market leader
pulls real, massive traffic (897 users/30 days, 100,000+ runs/30 days) but
sits at a 3.8-star rating; a paid alternative in the same space converts at
roughly 63 users/30 days. Read plainly: the demand is proven, the free
option's own rating shows real reliability complaints, and most of the
1,700 results are unmaintained hobby projects, not polished products. The
bet here is not to win a price war against a free tool. It is to be the one
that gets full-page capture right, gives real format choice, and reports
honest, decoded-not-guessed metadata about what it actually captured, at a
fair price.

### What it does

- Launches a real headless Chrome (Playwright) per run and opens a fresh,
  isolated browser context per URL: no cookies or session state carried
  between captures.
- `fullPage` (default on) captures the entire scrollable page top to
  bottom; off captures just the viewport, cut at `viewportHeight`.
- Four output formats: PNG (lossless), JPEG, WebP, and PDF (prints the page
  as a single page sized to the capture, not paginated like a normal
  printout).
- Two ways to wait for slow-loading content: `waitForSelector` (wait for a
  specific CSS selector to appear, the reliable choice) or `delayMs` (a
  simple fixed wait, the blunt choice).
- The captured file goes to the Actor's default key-value store, the
  correct Apify pattern for binary output; the dataset record carries a
  ready-to-fetch URL to it, never a base64 blob bloating the dataset.
- Every `width`/`height` in the output is decoded from the captured file's
  own header (PNG IHDR, JPEG SOF, WebP VP8/VP8X/VP8L, PDF MediaBox), not
  estimated from the page's layout moments before the shot. Real pages
  reflow between "measure" and "capture" (a lazy image finishes loading, a
  web font swaps in) often enough that a pre-capture guess is not honest to
  publish; decoding the actual bytes stored removes the guess entirely.

### What it deliberately does not do

- **No login or paywall bypass.** This Actor renders pages exactly as a
  normal visitor's browser would: the real default Chrome user agent, no
  cookie injection, no credential handling. It captures only what is
  already reachable without an account. If a page shows a login wall to a
  normal visitor, that is exactly what gets captured, honestly, not
  something this Actor tries to defeat.
- **No bot-detection evasion.** Some sites fingerprint headless Chrome and
  serve a block page or a challenge instead of real content. This Actor
  makes no attempt to spoof past that. If a target site blocks headless
  browsers, that shows up in the captured image (or as a `status: "error"`
  record), not as a silently wrong result.
- **No infinite waits.** Navigation uses Chromium's `load` event, not
  `networkidle`: real sites keep background connections open forever
  (analytics beacons, polling, websockets), and waiting for total network
  silence would turn ordinary pages into guaranteed timeouts. For content
  that renders after the load event, use `waitForSelector`.

### Output example

Real records from test runs, local and cloud.

A full-page PNG, from a real cloud run (the `assetUrl` was fetched back and
independently verified: valid PNG signature, IHDR-decoded dimensions
1280x2590 matching this record exactly, byte-for-byte `Content-Length`
match):

```json
{
  "url": "https://en.wikipedia.org/wiki/Web_page",
  "format": "png",
  "fullPage": true,
  "width": 1280,
  "height": 2590,
  "fileSizeBytes": 616121,
  "assetKey": "screenshot-002-en-wikipedia-org-wiki-web-page.png",
  "assetUrl": "https://api.apify.com/v2/key-value-stores/hK1paxusDVdWktyRR/records/screenshot-002-en-wikipedia-org-wiki-web-page.png?signature=1urIXoaOMUoJMYEhbXNqI",
  "httpStatusCode": 200,
  "status": "ok",
  "error": null,
  "capturedAt": "2026-08-25T23:35:17.765Z"
}
```

A malformed URL, caught and charged nothing:

```json
{
  "url": "not-a-url",
  "format": "png",
  "fullPage": true,
  "width": null,
  "height": null,
  "fileSizeBytes": null,
  "assetKey": null,
  "assetUrl": null,
  "httpStatusCode": null,
  "status": "error",
  "error": "Only http(s) URLs are supported.",
  "capturedAt": "2026-08-25T23:28:39.347Z"
}
```

**Getting the actual image**: open `assetUrl` directly, or fetch it
yourself; it is a plain `GET` to the key-value store's record endpoint,
`https://api.apify.com/v2/key-value-stores/{storeId}/records/{assetKey}`,
which returns the raw file with the right `Content-Type` already set. No
separate lookup needed to find the store ID first, `assetUrl` already has
it baked in.

### Input

| Field | Meaning |
| --- | --- |
| `urls` | Pages to capture. Required. |
| `fullPage` | On (default): entire scrollable page. Off: just the viewport, cut at `viewportHeight`. |
| `viewportWidth` / `viewportHeight` | Browser viewport before capture, default 1280x800. |
| `format` | `png` (default, lossless), `jpeg`, `webp`, or `pdf`. |
| `waitForSelector` | CSS selector to wait for before capturing, for content that renders after a JavaScript call. Optional. |
| `delayMs` | Simple fixed wait before capturing, default 0. Simpler than `waitForSelector`, blunter: it waits the full amount regardless of whether the page needed it. |
| `quality` | JPEG/WebP compression quality, 1-100, default 90. Ignored for png (always lossless) and pdf. |
| `pageTimeoutSecs` | Wall-clock budget per URL, covering navigation, the wait strategy, and the capture itself. Default 30, 5-120. A page over budget is `status: "timeout"` and is never charged. |

### Status values

`ok`, `error` (bad URL, DNS failure, navigation error), or `timeout`
(`pageTimeoutSecs` ran out during navigation, the wait strategy, or the
capture itself). Every non-`ok` record's `error` field says exactly what
happened. Only `ok` records are charged.

A page that loads but returns a non-2xx HTTP status (a site's own 404 or
500 page) is still `status: "ok"` and is charged: the browser rendered
exactly what a visitor would see, and that's the point of a screenshot.
Check the `httpStatusCode` field if you need to tell these apart from a
normal 200 response.

### Typical uses

- Visual regression snapshots for a set of pages, on a schedule.
- Feeding an AI agent a real rendered view of a page instead of raw HTML.
- Archiving how a page looked on a given date (each record's `capturedAt`
  is exact).
- Generating a PDF "print view" of a page without opening a browser
  yourself.

### Known limitations, disclosed honestly

- **JS-heavy pages may need `waitForSelector` tuning.** The default `load`
  wait covers images, stylesheets, and iframes, but content that renders
  from a JavaScript call after that point needs an explicit selector (or a
  `delayMs`) to exist by the time the shot is taken. This is how the input
  is designed to be used, not a gap to fix later.
- **Bot detection can block a headless browser.** Some sites specifically
  fingerprint and block headless Chrome. This Actor does not attempt to
  spoof past that; a blocked page is captured (or errors) exactly as
  described above, never silently faked into looking successful.
- **Extremely long pages can hit a browser canvas limit.** Full-page
  capture of a page many tens of thousands of pixels tall can exceed
  Chromium's own rendering limits; that surfaces as a `status: "error"`
  record with the underlying reason, not a corrupt file.
- **PDF is single-page by design.** `format: "pdf"` renders the capture as
  one page sized to the content, not a normal multi-page printout: this
  matches "PDF as an alternative screenshot format," not "print this
  article for reading."

### Fair pricing

Pay per screenshot successfully captured, once pay-per-event pricing is
enabled. A URL that errors or times out costs nothing. No subscription.

# Actor input Schema

## `urls` (type: `array`):

Pages to capture. Each is opened in a real headless Chrome browser and rendered exactly as a normal visitor would see it: this Actor does not bypass logins, paywalls, or any other access control. Supply only pages you have the right to capture.

## `fullPage` (type: `boolean`):

On (default): capture the entire scrollable page, top to bottom. Off: capture only the current viewport, cut off at viewportHeight.

## `viewportWidth` (type: `integer`):

Browser viewport width before capture. Common presets: 1280 (laptop), 1920 (desktop), 375 (mobile portrait), 768 (tablet portrait).

## `viewportHeight` (type: `integer`):

Browser viewport height before capture. Only the visible cutoff when fullPage is off; fullPage captures ignore this and use the page's real scroll height instead.

## `format` (type: `string`):

Output file format. PNG is lossless and the safest default; JPEG and WebP are smaller and take the quality setting; PDF prints the page instead of rastering it.

## `waitForSelector` (type: `string`):

Optional. A CSS selector (e.g. ".hero-image", "#chart-loaded") the Actor waits to appear before capturing. Use this for pages whose real content renders after a JavaScript call; it is the reliable alternative to a fixed delay. Leave empty to skip.

## `delayMs` (type: `integer`):

Optional. A simple fixed wait after the page loads (and after waitForSelector resolves, if both are set) before capturing. Simpler than waitForSelector but blunter: it always waits the full amount, whether the page needed it or not. Default 0 (no extra wait).

## `quality` (type: `integer`):

Compression quality, 1 to 100. Only applies to jpeg and webp; ignored for png (always lossless) and pdf.

## `pageTimeoutSecs` (type: `integer`):

Wall-clock budget per URL, covering navigation, the wait strategy, and the capture itself. A page that blows the budget is reported with status "timeout" and is never charged.

## Actor input object example

```json
{
  "urls": [
    "https://example.com"
  ],
  "fullPage": true,
  "viewportWidth": 1280,
  "viewportHeight": 800,
  "format": "png",
  "delayMs": 0,
  "quality": 90,
  "pageTimeoutSecs": 30
}
```

# Actor output Schema

## `records` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://example.com",
        "https://en.wikipedia.org/wiki/Web_page"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("inn_corp/quality-website-screenshot-api").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "urls": [
        "https://example.com",
        "https://en.wikipedia.org/wiki/Web_page",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("inn_corp/quality-website-screenshot-api").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://example.com",
    "https://en.wikipedia.org/wiki/Web_page"
  ]
}' |
apify call inn_corp/quality-website-screenshot-api --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,inn_corp/quality-website-screenshot-api"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/uf4yBSq9pROehgdFg/builds/ekMwICa0hoxqRo6on/openapi.json
