# xHamster Scraper (`solidcode/xhamster-scraper`) Actor

\[💰 $2 / 1K] Extract public xHamster video metadata — title, tags, categories, duration, views, likes, rating, upload date, channel, thumbnail. Scrape by keyword search or paste video, search, category, tag, channel, and pornstar URLs. One flat row per video. No media/download links.

- **URL**: https://apify.com/solidcode/xhamster-scraper.md
- **Developed by:** [SolidCode](https://apify.com/solidcode) (community)
- **Categories:** Videos, Developer tools, Other
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 results

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/platform/actors/running/actors-in-store#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.

In JavaScript/TypeScript projects, use official [JavaScript/TypeScript client](https://docs.apify.com/api/client/js/docs.md):

```bash
npm install apify-client
```

In Python projects, use official [Python client library](https://docs.apify.com/api/client/python/docs.md):

```bash
pip install apify-client
```

In shell scripts, use [Apify CLI](https://docs.apify.com/cli/docs.md):

````bash
# MacOS / Linux
curl -fsSL https://apify.com/install-cli.sh | bash
# Windows
irm https://apify.com/install-cli.ps1 | iex
```bash

In AI frameworks, you might use the [Apify MCP server](https://docs.apify.com/integrations/mcp.md).

If your project is in a different language, use 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

## xHamster Scraper

Pull public xHamster video metadata at scale — 23 structured fields per video, seven input surfaces, canonical category and tag names, and one flat row per result. This is a catalog-metadata tool, not a downloader: no video files, download links, streaming manifests, embed codes, or animated previews are ever returned. Built for trend analysts, taxonomy and catalog researchers, SEO teams, and content-moderation researchers who need a clean, structured picture of what a platform publishes without ever touching the media itself.

### Why This Scraper?

- **Metadata only, by design** — the output carries titles, counts, dates, and taxonomy labels. No video files, no download links, no streaming manifests, no embed codes, no animated previews, no comment text. That boundary is enforced in the output itself, which makes the dataset safe to hand to legal, compliance, and research teams.
- **7 input surface types in one run** — keyword search plus pasted links to video, search, category, tag, channel, and performer pages. User pages are auto-normalized to their video listing, so a bare profile link still returns that account's uploads.
- **23 metadata fields per video** — video id, title, description, duration in seconds and as `mm:ss`, views, likes, dislikes, rating percent, comment count, upload date, categories, tags, performer labels, channel name and URL, uploader name and URL, and a still poster image.
- **Canonical, non-localized category and tag names** — taxonomy labels come back in their canonical form rather than the page's display language, so tag frequency counts stay directly comparable across runs, regions, and languages.
- **5 sort orders that genuinely change the result set** — Most Relevant, Newest First, Most Viewed, Top Rated, and Longest. Verified independently: the top result differs under every one of the five.
- **Terminus-aware pagination** — a source is walked to its true last page rather than stopping at the page window the site displays. A single tag was walked across all 19 of its pages in one run for 833 videos, with zero duplicate video ids.
- **Two independent result caps** — a global ceiling for the whole run and a per-link ceiling for each individual search or listing page, so one channel holding thousands of videos can never dominate a multi-source run.
- **Optional detail enrichment, on a switch** — off for fast catalog sweeps (title, link, duration, views, upload date, thumbnail, uploader); on to add description, tags, categories, performers, likes, dislikes, rating percent, and comment count to every row.
- **833 videos from one tag in 33 seconds** — that is a measured catalog sweep across all 19 pages of a tag with detail enrichment off. Switch enrichment on and the same tag returns 808 fully enriched rows instead, so you can pick breadth or depth per run.

### Use Cases

**Content Trend & Market Analysis**
- Track which categories and tags dominate Most Viewed and Top Rated listings over time
- Measure view, like, and rating distributions across a topic to gauge audience demand
- Re-run a saved search on a schedule to build view-count and engagement time series
- Compare upload volume by month using normalized upload dates

**Catalog & Taxonomy Research**
- Snapshot a channel's or performer's full back catalog into a single sheet
- Map how categories and tags co-occur to build or audit a taxonomy
- Measure tag cardinality and label reuse across thousands of videos
- Build a reference index of public listings for internal cataloging systems

**SEO & Keyword Research**
- Compare which keyword searches surface high-view versus low-view catalogs
- Extract title and description text at volume for term-frequency analysis
- Identify long-tail tags with meaningful view counts but thin competition
- Benchmark title length, phrasing, and tag density against top-performing results

**Content Moderation & Compliance Research**
- Compile public metadata sets for content-policy and platform-governance studies
- Track category and tag prevalence over time for safety and risk research
- Assemble reproducible reference sets of public listings for academic or legal review
- Audit how a platform labels and classifies its own catalog

**Competitive Catalog Benchmarking**
- Compare upload cadence, catalog size, and engagement across channels
- Contrast tagging strategies between studios and independent uploaders
- Rank channels by median views, rating percent, or comment volume
- Feed structured rows into BI dashboards for ongoing category monitoring

### Getting Started

#### Simple Keyword Search

One keyword, defaults for everything else — 100 videos with full detail:

```json
{
    "searchQueries": ["yoga"]
}
````

#### Fast Catalog Sweep

Detail enrichment off for a wide, quick listing pass ordered by newest uploads:

```json
{
    "searchQueries": ["yoga", "fitness"],
    "maxResults": 500,
    "maxResultsPerSource": 250,
    "sortBy": "newest",
    "includeDetails": false
}
```

#### Mixed Links and Keywords

Combine several page types with a keyword search in one run:

```json
{
    "startUrls": [
        "https://xhamster.com/categories/massage",
        "https://xhamster.com/tags/yoga",
        "https://xhamster.com/videos/xhsaQFO"
    ],
    "searchQueries": ["fitness"],
    "maxResults": 400,
    "maxResultsPerSource": 100,
    "sortBy": "best",
    "includeDetails": true
}
```

#### Full Catalog Pull

Every video a single channel has published, with no ceiling other than the safety limit:

```json
{
    "startUrls": ["https://xhamster.com/channels/example-studio"],
    "maxResults": 0,
    "maxResultsPerSource": 0,
    "sortBy": "newest",
    "includeDetails": true
}
```

### Input Reference

#### What to Scrape

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `startUrls` | array | `[]` | Paste one or more xHamster links. A single video link resolves to one result. A search, category, tag, channel, pornstar, or user page collects every video listed on it, page by page. You can mix different link types, and you can add keywords below as well. |
| `searchQueries` | array | `[]` | Keywords to search for, such as a topic, a studio, or a performer name. Each keyword runs its own search and the matching videos are collected. Use this on its own or alongside the links above. |

Leave both empty and the run finishes immediately without collecting anything.

#### Limits

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `maxResults` | integer | `100` | The most videos to collect in total, across every link and keyword search. Each video is one result. Set to `0` for no limit — runs then stop at a safety ceiling of 100,000 videos. |
| `maxResultsPerSource` | integer | `50` | The most videos to take from any single search, category, tag, channel, pornstar, or user page. Some channels hold many thousands of videos, so this keeps each one in check. This is ignored for links that point at a single video. Set to `0` to remove this cap entirely — the overall maximum above still applies. |

#### Options

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `sortBy` | string | `"relevance"` | How to order the videos collected from searches and listing pages. Options: `relevance` (Most Relevant), `newest` (Newest First), `views` (Most Viewed), `best` (Top Rated), `longest` (Longest). Does not apply to links pointing at a single video. |
| `includeDetails` | boolean | `true` | When on, each video's own page is opened to collect its tags, categories, performers, rating, comment count, and description. When off, the run is much faster and cheaper but each result carries only the title, link, duration, views, upload date, thumbnail, and uploader. |

### Output

Every result is one flat row. Arrays hold plain label strings — no nested objects to unpack.

```json
{
    "recordType": "video",
    "videoId": "29147704",
    "url": "https://xhamster.com/videos/example-title-29147704",
    "title": "Full Body Stretch Routine",
    "description": "A guided routine recorded in a single take.",
    "duration": 1462,
    "durationFormatted": "24:22",
    "views": 184920,
    "likes": 1843,
    "dislikes": 122,
    "ratingPercent": 94,
    "commentCount": 37,
    "uploadDate": "2024-11-08T14:22:41+00:00",
    "categories": ["Massage"],
    "tags": ["yoga", "stretching", "fitness"],
    "pornstars": ["Performer Name"],
    "channelName": "Example Studio",
    "channelUrl": "https://xhamster.com/channels/example-studio",
    "uploaderName": "example-account",
    "uploaderUrl": "https://xhamster.com/users/example-account",
    "thumbnailUrl": "https://thumb-v1.xhcdn.com/a/example/1280x720.jpg",
    "sourceUrl": "https://xhamster.com/search/yoga",
    "scrapedAt": "2026-07-24T09:14:02.881204+00:00"
}
```

#### Core Fields

| Field | Type | Description |
|-------|------|-------------|
| `recordType` | string | Always `"video"` — one record type, so every row shares the same shape |
| `videoId` | string | The platform's canonical numeric video id; stable across runs and used for deduplication |
| `url` | string | Public page URL for the video |
| `title` | string | Video title as published |
| `description` | string | Description text from the video page (detail enrichment only) |
| `duration` | integer | Runtime in seconds |
| `durationFormatted` | string | Same runtime as `mm:ss`, or `h:mm:ss` past an hour |
| `uploadDate` | string | Publication date and time in ISO 8601 |
| `thumbnailUrl` | string | Still poster image (JPG or WebP), largest available size |

#### Engagement Metrics

| Field | Type | Description |
|-------|------|-------------|
| `views` | integer | Total view count at the moment of collection |
| `likes` | integer | Positive votes (detail enrichment only) |
| `dislikes` | integer | Negative votes (detail enrichment only) |
| `ratingPercent` | integer | Approval rating from 0 to 100, as the platform reports it (detail enrichment only) |
| `commentCount` | integer | Number of comments on the video — a count only, no comment text (detail enrichment only) |

#### Taxonomy

| Field | Type | Description |
|-------|------|-------------|
| `categories` | string\[] | Canonical, non-localized category labels (detail enrichment only) |
| `tags` | string\[] | Canonical, non-localized tag labels (detail enrichment only) |
| `pornstars` | string\[] | Performer names credited on the video page (detail enrichment only) |

#### Channel & Uploader

| Field | Type | Description |
|-------|------|-------------|
| `channelName` | string | Publishing channel or studio name, when the video belongs to one |
| `channelUrl` | string | Public URL of that channel |
| `uploaderName` | string | Account that uploaded the video, for user-submitted uploads |
| `uploaderUrl` | string | Public URL of that account |

#### Run Context

| Field | Type | Description |
|-------|------|-------------|
| `sourceUrl` | string | The page this row came from, always as a URL — keyword searches report their own search URL, so every row groups cleanly by source after export |
| `scrapedAt` | string | Collection timestamp in ISO 8601 |

### Tips for Best Results

- **Start with 10–20 results.** Run one keyword at a small cap first to confirm the fields match your analysis, then scale up. Note that collection always finishes the page it is on, so a cap of 10 typically returns the full 46-video page rather than exactly 10.
- **Turn `includeDetails` off for breadth, on for depth.** A listing-only sweep is dramatically faster — the same 830-video tag takes about half a minute with enrichment off and roughly 17 minutes with it on, because every video's own page has to be opened. Leave it off for title, duration, view and upload-date analysis; switch it on only when you need tags, categories, performers, rating, or comment counts.
- **Match the sort to the question.** Most Viewed and Top Rated for demand and quality analysis, Newest First for monitoring fresh uploads, Longest to isolate full-length content, Most Relevant for the broadest keyword coverage.
- **Power-user tip: Most Relevant reaches far deeper than the other sorts.** For keyword searches the platform serves roughly twice as many pages under Most Relevant as under any other order. For an exhaustive keyword sweep run Most Relevant first, then re-run the same keyword under Newest First to pick up recent uploads that ranked too low to appear.
- **Listing pages support a narrower sort vocabulary than search.** Category, tag, channel, and performer pages honour Most Relevant, Newest First, and Top Rated; Most Viewed and Longest fall back to the page's own default order, and the run log tells you when that happens.
- **Use `maxResultsPerSource` to keep multi-source runs balanced.** With ten links and a global cap of 1,000, a per-source cap of 100 guarantees an even sample instead of one large channel consuming the entire budget.
- **Pivot on `categories` and `tags` directly.** Because those labels are canonical rather than translated, you can concatenate datasets collected days or regions apart and the frequency counts still line up.
- **Set both caps to `0` for a complete catalog.** That collects everything a channel, tag, or category holds, bounded only by the 100,000-video safety ceiling. For very large catalogs, split the work across several runs or raise the run's time limit in the run options.

### Pricing

**From $2.00 per 1,000 results** — a flat per-result rate for structured catalog metadata, with no charge for the pages walked to find it. Bronze, Silver, and Gold subscribers pay progressively less; the table below shows the total cost at each discount tier.

| Results | No discount | Bronze | Silver | Gold |
|---------|-------------|--------|--------|------|
| 100 | $0.24 | $0.23 | $0.22 | $0.20 |
| 1,000 | $2.40 | $2.25 | $2.15 | $2.00 |
| 10,000 | $24.00 | $22.50 | $21.50 | $20.00 |
| 100,000 | $240.00 | $225.00 | $215.00 | $200.00 |

A "result" is one video row in the output dataset. No compute or time-based charges — you pay per result, plus a small fixed per-run start fee.

### Integrations

Export data in JSON, CSV, Excel, XML, or RSS. Connect to 1,500+ apps via:

- **Zapier** / **Make** / **n8n** — Workflow automation
- **Google Sheets** — Direct spreadsheet export
- **Slack** / **Email** — Notifications on new results
- **Webhooks** — Trigger custom APIs on run completion
- **Apify API** — Full programmatic access

### Legal & Ethical Use

This actor collects only publicly available video metadata — titles, counts, dates, taxonomy labels, and a still poster image. It does not collect, produce, or distribute media files, download links, streaming manifests, or embed codes, and it is not a download tool.

It is intended for legitimate research, analytics, cataloging, and compliance work. xHamster is an adult platform, so this actor and its output are for adults aged 18 or older, and you are responsible for complying with the age-verification and access rules that apply in your jurisdiction — several countries and US states restrict access to adult platforms.

Use the collected data only for your own lawful, compliant purposes, and respect xHamster's Terms of Service. Performer and uploader names may constitute personal data under GDPR and similar regimes; if you store or process them, ensure you have a valid lawful basis and honour any deletion or objection requests. Do not use extracted data for spam, harassment, profiling of individuals, or any illegal purpose, and do not redistribute platform media in any form.

# Actor input Schema

## `startUrls` (type: `array`):

Paste one or more xHamster links. A single video link resolves to one result. A search, category, tag, channel, pornstar, or user page collects every video listed on it, page by page. You can mix different link types, and you can add keywords below as well.

## `searchQueries` (type: `array`):

Keywords to search for, such as a topic, a studio, or a performer name. Each keyword runs its own search and the matching videos are collected. Use this on its own or alongside the links above.

## `maxResults` (type: `integer`):

The most videos to collect in total, across every link and keyword search. Each video is one result. Set to 0 for no limit — runs then stop at a safety ceiling of 100,000 videos. Lower this to keep runs small and costs predictable.

## `maxResultsPerSource` (type: `integer`):

The most videos to take from any single search, category, tag, channel, pornstar, or user page. Some channels hold many thousands of videos, so this keeps each one in check. This is ignored for links that point at a single video. Set to 0 to remove this cap entirely — the overall maximum above still applies.

## `sortBy` (type: `string`):

How to order the videos collected from searches and listing pages. Keyword searches support every option. Category, tag, channel and performer pages support Most Relevant, Newest First and Top Rated — the other options fall back to the page's own default order. This does not apply to links that point at a single video.

## `includeDetails` (type: `boolean`):

When on, each video's own page is opened to collect its tags, categories, performers, rating, comment count, and description. When off, the run is much faster and cheaper but each result carries only the title, link, duration, views, upload date, thumbnail, and uploader.

## Actor input object example

```json
{
  "startUrls": [],
  "searchQueries": [],
  "maxResults": 100,
  "maxResultsPerSource": 50,
  "sortBy": "relevance",
  "includeDetails": true
}
```

# Actor output Schema

## `overview` (type: `string`):

Table of collected videos with the key fields — title, channel, duration, views, rating, upload date, categories, and link.

## `detail` (type: `string`):

Table of the enrichment fields collected when 'Collect full details for each video' is on — description, categories, tags, performers, likes, dislikes, rating, and comment count.

# 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 = {
    "startUrls": [],
    "searchQueries": [],
    "maxResults": 100,
    "maxResultsPerSource": 50,
    "sortBy": "relevance",
    "includeDetails": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("solidcode/xhamster-scraper").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 = {
    "startUrls": [],
    "searchQueries": [],
    "maxResults": 100,
    "maxResultsPerSource": 50,
    "sortBy": "relevance",
    "includeDetails": True,
}

# Run the Actor and wait for it to finish
run = client.actor("solidcode/xhamster-scraper").call(run_input=run_input)

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

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

```

## CLI example

```bash
echo '{
  "startUrls": [],
  "searchQueries": [],
  "maxResults": 100,
  "maxResultsPerSource": 50,
  "sortBy": "relevance",
  "includeDetails": true
}' |
apify call solidcode/xhamster-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=solidcode/xhamster-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

```json
{
    "openapi": "3.0.1",
    "info": {
        "title": "xHamster Scraper",
        "description": "[💰 $2 / 1K] Extract public xHamster video metadata — title, tags, categories, duration, views, likes, rating, upload date, channel, thumbnail. Scrape by keyword search or paste video, search, category, tag, channel, and pornstar URLs. One flat row per video. No media/download links.",
        "version": "1.0",
        "x-build-id": "BOHzVr6mQfOJPslng"
    },
    "servers": [
        {
            "url": "https://api.apify.com/v2"
        }
    ],
    "paths": {
        "/acts/solidcode~xhamster-scraper/run-sync-get-dataset-items": {
            "post": {
                "operationId": "run-sync-get-dataset-items-solidcode-xhamster-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for its completion, and returns Actor's dataset items in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        },
        "/acts/solidcode~xhamster-scraper/runs": {
            "post": {
                "operationId": "runs-sync-solidcode-xhamster-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor and returns information about the initiated run in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/runsResponseSchema"
                                }
                            }
                        }
                    }
                }
            }
        },
        "/acts/solidcode~xhamster-scraper/run-sync": {
            "post": {
                "operationId": "run-sync-solidcode-xhamster-scraper",
                "x-openai-isConsequential": false,
                "summary": "Executes an Actor, waits for completion, and returns the OUTPUT from Key-value store in response.",
                "tags": [
                    "Run Actor"
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/inputSchema"
                            }
                        }
                    }
                },
                "parameters": [
                    {
                        "name": "token",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Enter your Apify token here"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK"
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "inputSchema": {
                "type": "object",
                "properties": {
                    "startUrls": {
                        "title": "xHamster links",
                        "type": "array",
                        "description": "Paste one or more xHamster links. A single video link resolves to one result. A search, category, tag, channel, pornstar, or user page collects every video listed on it, page by page. You can mix different link types, and you can add keywords below as well.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "searchQueries": {
                        "title": "Keyword searches",
                        "type": "array",
                        "description": "Keywords to search for, such as a topic, a studio, or a performer name. Each keyword runs its own search and the matching videos are collected. Use this on its own or alongside the links above.",
                        "items": {
                            "type": "string"
                        }
                    },
                    "maxResults": {
                        "title": "Maximum videos",
                        "minimum": 0,
                        "type": "integer",
                        "description": "The most videos to collect in total, across every link and keyword search. Each video is one result. Set to 0 for no limit — runs then stop at a safety ceiling of 100,000 videos. Lower this to keep runs small and costs predictable."
                    },
                    "maxResultsPerSource": {
                        "title": "Maximum videos per link or search",
                        "minimum": 0,
                        "type": "integer",
                        "description": "The most videos to take from any single search, category, tag, channel, pornstar, or user page. Some channels hold many thousands of videos, so this keeps each one in check. This is ignored for links that point at a single video. Set to 0 to remove this cap entirely — the overall maximum above still applies."
                    },
                    "sortBy": {
                        "title": "Sort order",
                        "enum": [
                            "relevance",
                            "newest",
                            "views",
                            "best",
                            "longest"
                        ],
                        "type": "string",
                        "description": "How to order the videos collected from searches and listing pages. Keyword searches support every option. Category, tag, channel and performer pages support Most Relevant, Newest First and Top Rated — the other options fall back to the page's own default order. This does not apply to links that point at a single video."
                    },
                    "includeDetails": {
                        "title": "Collect full details for each video",
                        "type": "boolean",
                        "description": "When on, each video's own page is opened to collect its tags, categories, performers, rating, comment count, and description. When off, the run is much faster and cheaper but each result carries only the title, link, duration, views, upload date, thumbnail, and uploader."
                    }
                }
            },
            "runsResponseSchema": {
                "type": "object",
                "properties": {
                    "data": {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string"
                            },
                            "actId": {
                                "type": "string"
                            },
                            "userId": {
                                "type": "string"
                            },
                            "startedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "finishedAt": {
                                "type": "string",
                                "format": "date-time",
                                "example": "2025-01-08T00:00:00.000Z"
                            },
                            "status": {
                                "type": "string",
                                "example": "READY"
                            },
                            "meta": {
                                "type": "object",
                                "properties": {
                                    "origin": {
                                        "type": "string",
                                        "example": "API"
                                    },
                                    "userAgent": {
                                        "type": "string"
                                    }
                                }
                            },
                            "stats": {
                                "type": "object",
                                "properties": {
                                    "inputBodyLen": {
                                        "type": "integer",
                                        "example": 2000
                                    },
                                    "rebootCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "restartCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "resurrectCount": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "computeUnits": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "options": {
                                "type": "object",
                                "properties": {
                                    "build": {
                                        "type": "string",
                                        "example": "latest"
                                    },
                                    "timeoutSecs": {
                                        "type": "integer",
                                        "example": 300
                                    },
                                    "memoryMbytes": {
                                        "type": "integer",
                                        "example": 1024
                                    },
                                    "diskMbytes": {
                                        "type": "integer",
                                        "example": 2048
                                    }
                                }
                            },
                            "buildId": {
                                "type": "string"
                            },
                            "defaultKeyValueStoreId": {
                                "type": "string"
                            },
                            "defaultDatasetId": {
                                "type": "string"
                            },
                            "defaultRequestQueueId": {
                                "type": "string"
                            },
                            "buildNumber": {
                                "type": "string",
                                "example": "1.0.0"
                            },
                            "containerUrl": {
                                "type": "string"
                            },
                            "usage": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "integer",
                                        "example": 1
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            },
                            "usageTotalUsd": {
                                "type": "number",
                                "example": 0.00005
                            },
                            "usageUsd": {
                                "type": "object",
                                "properties": {
                                    "ACTOR_COMPUTE_UNITS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATASET_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "KEY_VALUE_STORE_WRITES": {
                                        "type": "number",
                                        "example": 0.00005
                                    },
                                    "KEY_VALUE_STORE_LISTS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_READS": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "REQUEST_QUEUE_WRITES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_INTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "DATA_TRANSFER_EXTERNAL_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_RESIDENTIAL_TRANSFER_GBYTES": {
                                        "type": "integer",
                                        "example": 0
                                    },
                                    "PROXY_SERPS": {
                                        "type": "integer",
                                        "example": 0
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
}
```
