# Website Screenshot Generator (`dami_studio/website-screenshot-generator`) Actor

Website screenshot generator for a list of websites, as PNG or JPEG: full page or first screen, width 320 to 2,560 px, retina, dark mode, phone layout. CSS selectors remove cookie banners or capture one element. Each row has the image link, final URL, title and status. $3 per 1,000 screenshots.

- **URL**: https://apify.com/dami_studio/website-screenshot-generator.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Developer tools, Automation, SEO tools
- **Features:** Limited permissions
- **Stats:** 2 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.85 / 1,000 screenshot takens

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

**Paste a list of web addresses and get a screenshot of each page, full page or first screen, as PNG or JPEG**: plus a row carrying the link to the image, the final URL after redirects, the page title, the HTTP status, the real image dimensions and the file size.

You control what the camera sees: the first screen or the whole page stitched together, any width from 320 to 2,560 pixels, normal or retina density, light or dark, desktop or phone layout. CSS selectors take out the cookie banner and the chat bubble before the shutter fires, or photograph one element on its own.

Worth knowing before the first run. A page that answers 404 or 500 and still renders is a real screenshot and is charged like any other, with `statusCode` on the row to tell you. A page that refuses to render at all still gets its image stored and handed to you, and that one is not charged.

| | |
|---|---|
| **Input** | Page addresses, up to 300 per run |
| **Output** | One image per page, and one row per page with its link, final URL, title, status code, dimensions and file size |
| **Ceiling** | 300 pages per run, inside a total image size budget you set |
| **Account needed** | None |
| **Price** | $3.00 per 1,000 screenshots ($0.003 each) on the Free plan, a little less on paid plans. The free plan's $5 a month covers about 1,600 |

### 🔍 What Website Screenshot Generator does

It opens each address in a real browser at the size you asked for, waits as long as you told it to, applies your CSS, and takes the picture. Images land in the run's key-value store.

Full-page capture scrolls the page and stitches it into one tall image, up to the height ceiling you set, and marks `truncated` when it had to cut. Scrolling also happens on request without full-page capture, which is what makes lazy-loaded images below the fold actually appear.

The selector fields do most of the work. `removeSelectors` takes an element out of the layout, which is the one for cookie banners and pop-ups. `hideSelectors` makes it invisible but keeps its space, which is right for a sticky header you want gone without everything shifting up. `selector` narrows the shot to one element.

`locale` and `timezone` are passed to the browser, so a site that localises renders in the language and clock you asked for. Duplicate addresses are dropped before anything renders, so a messy list does not cost twice.

### 📋 What data you get from each screenshot

| What you get | Field |
|---|---|
| A link to the image, and its name in the key-value store | `screenshotUrl`, `screenshotKey` |
| What you asked for, and where it ended up after redirects | `url`, `finalUrl` |
| The page title and the main document's status code | `title`, `statusCode` |
| The real image size, read from the file | `imageWidth`, `imageHeight` |
| How tall the page was, and whether the shot was cut | `pageHeight`, `truncated` |
| File size and how long the page took | `fileSizeBytes`, `loadTimeMs` |
| The settings this shot was taken with | `format`, `fullPage`, `darkMode`, `deviceScaleFactor`, `viewportWidth`, `viewportHeight` |

### ▶️ How to take screenshots of a list of websites

1. Open [Website Screenshot Generator](https://apify.com/dami_studio/website-screenshot-generator) and click **Try for free**.
2. Put your addresses into **Page addresses**, one per line.
3. Set **Maximum screenshots** low for the first run, so you can check the framing before paying for 300 of them.
4. Add the cookie banner and chat bubble to **Remove these elements**. That one change improves more screenshots than any other setting here.
5. Click **Start**, then open the rows to see the images, or pull them from the key-value store.

Leave **Page addresses** empty and you get one sample row showing the shape, with no browser started and nothing charged.

### 💰 How much does it cost to take a screenshot?

**$3.00 per 1,000 screenshots** on the Free plan, $2.95 on Starter, $2.90 on Scale and $2.85 on Business. The free plan's $5 a month covers about 1,600.

You pay per image taken. The sample row is not charged, nor are the diagnostics: a page that was refused, had no matching selector, could not be reached, or was skipped once the size budget ran out. A page that rendered is charged whatever its status code says.

Timeouts split two ways. A page that hits `navigationTimeoutMs` with enough on screen is still captured, and charged like any other. One with nothing usable gets an uncharged `NAV_TIMEOUT` row.

If you set a maximum charge for a run, it stops taking pictures once that is reached.

### 📥 What you give it

```json
{
  "urls": ["https://example.com", "https://news.ycombinator.com"],
  "fullPage": true,
  "viewportWidth": 1440,
  "format": "jpeg",
  "quality": 80,
  "removeSelectors": ["#cookie-banner", ".intercom-launcher"],
  "maxItems": 10
}
```

| Field | Default | What it is |
|---|---|---|
| `urls` | none; the box starts with two examples | The pages, one per line. A bare domain works. Up to 300. |
| `maxItems` | the whole list; the form starts at `10` | Hard stop on how many pages this run photographs, 1 to 300. An API call that leaves it out photographs the whole list. Keep it low while testing. |
| `fullPage` | `false` | Off gives you exactly the viewport. On scrolls and stitches the whole page. |
| `viewportWidth`, `viewportHeight` | `1440`, `900` | The browser window. 390 wide is a phone, 1920 a desktop monitor. |
| `deviceScaleFactor` | `1` | 2 is retina, 3 is print. Remember 2x is four times the file size. |
| `format`, `quality` | `jpeg`, `80` | JPEG is much smaller. PNG is lossless and can carry transparency. Quality applies to JPEG only. |
| `darkMode`, `mobile` | `false` | Ask the page for its dark theme, and render as a touch phone. Pair `mobile` with a narrow width. |
| `waitUntil` | `domcontentloaded` | How ready the page must be: `commit`, `domcontentloaded`, `load` or `networkidle`. On an ad-funded page `networkidle` often waits for the timeout. |
| `delayMs` | `800` | Extra pause for animations and fonts, up to 15 seconds. |
| `navigationTimeoutMs` | `25000` | Give up on a page that has not become ready in this long. If enough of the page exists by then, it is still captured. |
| `scrollToBottom` | `false` | Scroll first so lazy images load. On automatically with `fullPage`. |
| `maxFullPageHeight` | `8000` | Tallest a full-page shot may be before it is cut, up to 20,000 px. |
| `maxTotalImageMb` | `250` | The run stops once it has written this many megabytes. Pages it never reached are not charged. |
| `selector` | empty | A CSS selector to photograph one element instead of the page. |
| `hideSelectors`, `removeSelectors` | empty | Up to 30 selectors each: hide keeps the space, remove takes it out of the layout. |
| `customCss` | empty | Raw CSS injected just before the shot, up to 4,000 characters. |
| `transparentBackground` | `false` | PNG only. Leaves the page background out, for dropping an element into a design. |
| `locale`, `timezone` | `en-US`, none | A language such as `de-DE`, and a zone such as `Europe/Berlin` or `America/New_York`. |
| `concurrency` | `3` | Pages rendered at once, 1 to 6. Drop to 1 for a site that objects. |
| `useProxy` | `false` | Optional. |
| `proxyUrls` | empty | Your own servers, as `http://user:pass@host:port`. Only the first entry in the list is used, and it takes precedence over everything else. |

### 📤 What you get back

A real row from a run on 3 October 2026, with the image link cut short. The page was taller than the window, so `pageHeight` is 1215 while the image, without full-page capture, is 900.

```json
{
  "ok": true,
  "charged": true,
  "recordType": "screenshot",
  "url": "https://news.ycombinator.com/",
  "screenshotUrl": "https://api.apify.com/v2/key-value-stores/.../records/screenshot-002-news.ycombinator.com.jpg?signature=...",
  "screenshotKey": "screenshot-002-news.ycombinator.com.jpg",
  "finalUrl": "https://news.ycombinator.com/",
  "title": "Hacker News",
  "statusCode": 200,
  "imageWidth": 1440,
  "imageHeight": 900,
  "format": "jpeg",
  "fullPage": false,
  "darkMode": false,
  "deviceScaleFactor": 1,
  "viewportWidth": 1440,
  "viewportHeight": 900,
  "pageHeight": 1215,
  "truncated": false,
  "fileSizeBytes": 123695,
  "loadTimeMs": 1525,
  "scrapedAt": "2026-10-03T00:26:08.195Z"
}
```

| Field | How to read it |
|---|---|
| `screenshotUrl` | A direct link to the image in the run's key-value store. Download what you want to keep. |
| `statusCode` | The main document's status. A page that rendered on a 404 says 404 here. |
| `imageWidth`, `imageHeight` | Read from the image itself, so this is the real picture size, density included. |
| `pageHeight`, `truncated` | How tall the document was, and whether the height ceiling cut a full-page shot. |
| `fileSizeBytes`, `loadTimeMs` | Useful for spotting the page that is quietly eating your size budget. |

### 🧾 Reading the output

`recordType` and `ok` separate the three kinds of row. `charged` is the flag that marks a real screenshot apart from the sample and the diagnostics; read it as that, not as a receipt.

| Row | How to spot it | Charged |
|---|---|---|
| A screenshot | `recordType` is `screenshot`, `ok` is true | yes |
| The sample row | `_sample` is true | no |
| A diagnostic | `_diagnostic` is true, with an `errorCode` | no |

The table view has no `errorCode` column, so a failed row can look like a thin successful one. A `NOT_SERVED` row even carries an image. Filter on `recordType` when you are counting.

| Code | What it means |
|---|---|
| `NOT_SERVED` | This page was not served this run. Re-run it later. Runs before 13 October 2026 wrote `BLOCKED` here. |
| `NAV_TIMEOUT` | Nothing usable had loaded inside `navigationTimeoutMs`. |
| `NAV_ABORTED` | Not a page the browser can display. A PDF or other file lands here. |
| `DNS_FAILED`, `CONNECTION_REFUSED`, `TLS_FAILED` | The address did not resolve, the server refused, or the certificate did not check out. |
| `SELECTOR_NOT_FOUND` | Your `selector` matched nothing on the page. |
| `IMAGE_TOO_LARGE` | The picture went past the encoder's ceiling of 16,000 pixels a side. |
| `STORAGE_FAILED` | The image could not be saved. |
| `BUDGET_REACHED` | `maxTotalImageMb` is spent. One row says so and the pages after it are skipped. |
| `TIME_BUDGET` | The run ran out of time before reaching that page. |
| `RENDER_FAILED`, `RENDERER_CRASHED` | The browser failed on that page. Worth one re-run. |
| `PROXY_INPUT_ADJUSTED` | A network option you set was not one this actor uses, so it carried on without it. |
| `CHARGE_ERROR`, `UNEXPECTED_ERROR` | Something went wrong in the run itself. Send us the run ID. |

### 💡 What people use it for

- Thumbnails for a directory or a link roundup, generated from the list you already have.
- Before and after shots of a redesign: same width, same settings, two runs.
- Visual regression checks on a schedule: same pages, same size, compare the images.
- Capturing one element cleanly, a pricing table or a chart, with a transparent background for a deck.
- Evidence and archiving, with `finalUrl`, `statusCode` and a timestamp on every row.

### 🚧 What it does not do

- **Nothing behind a login.** No cookies, no sign-in, no sessions.
- **300 pages per run.** Split a longer list, or schedule it.
- **The page is frozen once it has painted**, so content that arrives very late can be missing.
- **Height and size ceilings.** 20,000 pixels of page by your setting, 16,000 pixels a side at the encoder, and the total megabytes you allowed.
- **No PDF output.** PNG and JPEG only.
- **No text extraction.** For the words on the page, use the crawler linked below.
- **It cannot make a site render.** A page that refuses gives you the image of the refusal, free.
- **Only the first entry** in your own server list is used.
- It is not an official tool of any platform or company named on this page. Dami's Studio is independent and is not affiliated with or endorsed by any of them.

### 🧭 Which site tool do you need?

| If you want | Use |
|---|---|
| Pictures of pages | This one |
| The same pages as text, Markdown and chunks | [Website Intelligence Crawler](https://apify.com/dami_studio/website-intelligence-crawler) |
| A site's DNS, certificate, security headers and tech signals | [Domain Inspector](https://apify.com/dami_studio/domain-inspector) |
| What changed on a rival's pricing page or changelog since last week | [SaaS Competitor Monitor](https://apify.com/dami_studio/saas-competitor-change-monitor) |
| The emails, phones and socials a site publishes | [Contact Details Scraper](https://apify.com/dami_studio/contact-details-scraper) |

### ❓ Questions people ask

#### How do I get rid of the cookie banner in a screenshot?

Put its CSS selector into **Remove these elements**. Open the site, inspect the banner, copy the id or class.

#### Why is my full-page screenshot cut off?

It hit `maxFullPageHeight`. The row says `truncated: true`. Raise the ceiling, but remember an endless feed has no bottom.

#### Can I screenshot one element?

Yes, with `selector`. Pair it with PNG and a transparent background to drop it straight into a design.

#### Where do the images live?

In the run's key-value store, linked from `screenshotUrl` on every row. Download the ones you want to keep.

#### Why do two runs of the same page look different?

Fonts, animations and ads settle at different speeds. Raise `delayMs`, or switch `waitUntil` to `load`, and the shots get more repeatable.

#### Can I call it from code or connect it to an AI assistant?

The [API tab](https://apify.com/dami_studio/website-screenshot-generator/api/python) has code for Python, JavaScript and the CLI. MCP clients like Claude and ChatGPT use `https://mcp.apify.com/?tools=fetch-actor-details,dami_studio/website-screenshot-generator`; coding assistants can load [our skill file](https://api.apify.com/v2/key-value-stores/BTBHU3DdiuoF4JOsK/records/apify-web-checks-monitors-SKILL.md). All three bill your Apify account at the same price. Send `maxItems` from code, or the whole list is photographed.

#### Is screenshotting a public page legal?

It photographs public pages as a visitor sees them. What you do with the images afterwards is subject to copyright and the site's terms. Apify's write-up on [the legality of web scraping](https://blog.apify.com/is-web-scraping-legal/) is a good starting point, and we are not lawyers.

### 🆘 If something breaks

Open the **Issues** tab on the actor page. Send the address and the run ID. If a diagnostic row landed, its `errorCode` and `details` usually name the reason already.

# Actor input Schema

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

One or more pages to photograph, one per line. A bare domain like example.com works too. Up to 300 per run, and duplicates are dropped before anything is charged.

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

Off: you get exactly the viewport, like the first screen a visitor sees. On: the page is scrolled and stitched into one tall image, down to the height ceiling below.

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

Browser window width the page is laid out at. 1440 is a typical laptop, 1920 a desktop monitor, 390 a phone. Between 320 and 2560.

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

Browser window height. This is the image height unless "Capture the whole page" is on. Between 200 and 4000.

## `deviceScaleFactor` (type: `integer`):

1 for a normal screen, 2 for a retina-quality image at twice the pixels on each axis, 3 for print. Remember that 2x is four times the file size and 3x is nine times.

## `darkMode` (type: `boolean`):

Tells the page the visitor prefers a dark colour scheme. Sites that ship a dark theme will render it; sites that do not are unchanged.

## `mobile` (type: `boolean`):

Renders as a touch phone, so you get the responsive layout. Pair it with a narrow viewport width such as 390.

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

JPEG is far smaller and right for almost everything. PNG is lossless and supports a transparent background.

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

Only applies to JPEG. 80 is visually clean at about a third of the size of 100. Lower it to fit more pages inside the run's size budget.

## `waitUntil` (type: `string`):

How ready the page must be before the shutter fires. "domcontentloaded" is the fast default. "load" waits for images and stylesheets. "networkidle" waits for traffic to stop, which on an ad-funded page can mean waiting for the timeout. "commit" fires almost immediately.

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

Pause after the page is ready, to let animations finish and fonts swap in. 800 ms suits most sites; raise it for a heavy single-page app.

## `navigationTimeoutMs` (type: `integer`):

Give up on a page that has not become ready in this long. The page still gets an uncharged row explaining the timeout.

## `scrollToBottom` (type: `boolean`):

Scrolls to the foot of the page first so lazy-loaded images below the fold are actually there in the picture. Turned on automatically when you capture the whole page.

## `maxFullPageHeight` (type: `integer`):

The tallest a whole-page capture may get before it is cut off. Protects you from an endless feed turning into a 60,000 pixel image. Rows tell you when a picture was cut.

## `maxTotalImageMb` (type: `integer`):

The run stops taking pictures once it has written this many megabytes and writes one uncharged row saying so. Nothing is charged for the pages it skipped.

## `maxItems` (type: `integer`):

Hard stop on how many pages this run will photograph, whatever the list length. Keep it low while you are testing - you pay per screenshot.

## `selector` (type: `string`):

A CSS selector. Fill it in to photograph just that element - a pricing table, a hero banner, a chart - instead of the page. Left empty, the whole viewport or page is captured.

## `hideSelectors` (type: `array`):

CSS selectors to make invisible before capturing, while keeping the space they occupy. Good for a sticky header you want gone without the layout shifting.

## `removeSelectors` (type: `array`):

CSS selectors to take out of the layout entirely. This is the one for cookie banners, chat bubbles and newsletter pop-ups.

## `customCss` (type: `string`):

Raw CSS injected just before the picture is taken. Anything you can write in a stylesheet works, up to 4,000 characters.

## `transparentBackground` (type: `boolean`):

PNG only. Leaves the page background out of the image, which is what you want when capturing a single element to drop into a design.

## `locale` (type: `string`):

The language the browser asks for, as a code such as en-US, de-DE or fr-FR. Sites that localise will render in that language.

## `timezone` (type: `string`):

An IANA time zone such as Europe/Berlin or America/New_York, for pages that print dates or clocks.

## `concurrency` (type: `integer`):

How many pages render simultaneously. 3 is a good balance. Lower it to 1 if a site you are capturing objects to several requests at once.

## `useProxy` (type: `boolean`):

Routes the run through Apify Proxy.

## `proxyUrls` (type: `array`):

Leave this empty unless you already pay for proxy servers and want the traffic to leave through them, in the form http://user:pass@host:port. Anything listed here takes precedence.

## Actor input object example

```json
{
  "urls": [
    "https://example.com",
    "https://news.ycombinator.com"
  ],
  "fullPage": false,
  "viewportWidth": 1440,
  "viewportHeight": 900,
  "deviceScaleFactor": 1,
  "darkMode": false,
  "mobile": false,
  "format": "jpeg",
  "quality": 80,
  "waitUntil": "domcontentloaded",
  "delayMs": 800,
  "navigationTimeoutMs": 25000,
  "scrollToBottom": false,
  "maxFullPageHeight": 8000,
  "maxTotalImageMb": 250,
  "maxItems": 10,
  "selector": "main .pricing-table",
  "transparentBackground": false,
  "locale": "en-US",
  "timezone": "Europe/Berlin",
  "concurrency": 3,
  "useProxy": false
}
```

# Actor output Schema

## `results` (type: `string`):

Every row in the default dataset: url, screenshotUrl, finalUrl, title, statusCode, imageWidth, imageHeight, format, fullPage, fileSizeBytes, loadTimeMs. An empty, blocked or unmatched run returns a single uncharged row explaining what happened instead.

# 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://news.ycombinator.com"
    ],
    "fullPage": false,
    "viewportWidth": 1440,
    "viewportHeight": 900,
    "deviceScaleFactor": 1,
    "darkMode": false,
    "mobile": false,
    "format": "jpeg",
    "quality": 80,
    "waitUntil": "domcontentloaded",
    "delayMs": 800,
    "navigationTimeoutMs": 25000,
    "scrollToBottom": false,
    "maxFullPageHeight": 8000,
    "maxTotalImageMb": 250,
    "maxItems": 10,
    "transparentBackground": false,
    "concurrency": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/website-screenshot-generator").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://news.ycombinator.com",
    ],
    "fullPage": False,
    "viewportWidth": 1440,
    "viewportHeight": 900,
    "deviceScaleFactor": 1,
    "darkMode": False,
    "mobile": False,
    "format": "jpeg",
    "quality": 80,
    "waitUntil": "domcontentloaded",
    "delayMs": 800,
    "navigationTimeoutMs": 25000,
    "scrollToBottom": False,
    "maxFullPageHeight": 8000,
    "maxTotalImageMb": 250,
    "maxItems": 10,
    "transparentBackground": False,
    "concurrency": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/website-screenshot-generator").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://news.ycombinator.com"
  ],
  "fullPage": false,
  "viewportWidth": 1440,
  "viewportHeight": 900,
  "deviceScaleFactor": 1,
  "darkMode": false,
  "mobile": false,
  "format": "jpeg",
  "quality": 80,
  "waitUntil": "domcontentloaded",
  "delayMs": 800,
  "navigationTimeoutMs": 25000,
  "scrollToBottom": false,
  "maxFullPageHeight": 8000,
  "maxTotalImageMb": 250,
  "maxItems": 10,
  "transparentBackground": false,
  "concurrency": 3
}' |
apify call dami_studio/website-screenshot-generator --silent --output-dataset

```

## MCP server setup

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

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/4eFLOgIenBptTtm2g/builds/gDyScjyrDXdc0LuJM/openapi.json
