# YouTube Search Scraper (`sfoln/youtube-search`)

Search YouTube by keyword with upload date, sort, type, duration and country filters: title, URL, channel, views, duration, publish date and Shorts or live flags, one JSON record per result. $0.40 per 1,000 results + $0.003 per page of ~20. No API key needed.

- Family: Social media data
- Categories: Social media, Videos, Automation
- Availability: on request (private on Apify). Email contact@sfoln.com with subject "Access request: YouTube Search Scraper (sfoln/youtube-search)" before calling; calls fail until it is public.
- JSON: https://www.sfoln.com/actors/youtube-search.json
- MCP: https://mcp.apify.com?tools=sfoln/youtube-search
- REST (sync): POST https://api.apify.com/v2/actors/sfoln~youtube-search/run-sync-get-dataset-items
- REST (async): POST https://api.apify.com/v2/actors/sfoln~youtube-search/runs

YouTube Search Scraper searches YouTube by keyword and returns one clean dataset record per video result: video ID, URL, title, channel (name, ID, handle, URL), views, duration, publish date, thumbnail and Short / live flags. You can filter by upload date, sort by relevance or popularity, limit results to videos or Shorts, filter by duration and search from a specific country. It's built for content research, SEO and keyword tracking, trend monitoring, influencer discovery and AI agents that need YouTube search results as JSON.

## Price per delivered record

| Event | Price | Per 1,000 | Charged for |
| --- | --- | --- | --- |
| YouTube search result (`youtube-search-result`) | $0.0004 | $0.40 / 1,000 | Charged once per video result (video, Short or live stream) delivered as a dataset record. Query summary records are never charged. |
| YouTube search page (`youtube-search-query`) | $0.003 | $3 / 1,000 | Charged once per page of search results fetched for a query (about 20 results per page) that delivered at least one result, or that the provider billed although it had no new results (e.g. a query with no results). Covers the per-request upstream fee. |

Pay-per-event, USD, billed by Apify; no platform usage charge. Only records with status `ok` are charged. Cap a run with `maxTotalChargeUsd`. Apify free plan: up to 10 per run.

## Run

```bash
curl -X POST "https://api.apify.com/v2/actors/sfoln~youtube-search/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"queries":["lofi hip hop"],"maxResultsPerQuery":10}'
```

MCP client configuration:

```json
{
  "mcpServers": {
    "apify": {
      "type": "http",
      "url": "https://mcp.apify.com?tools=sfoln/youtube-search"
    }
  }
}
```

Example prompt: Search YouTube for "lofi hip hop" videos uploaded this week, sorted by popularity, and list the top 10 with their channels and views.

## Input

Enter one or more search queries. Each query returns one dataset record per video result (videos, Shorts, live streams) plus one summary record per query.

| Field | Type | Default | Meaning |
| --- | --- | --- | --- |
| `queries` (required) | array | — | Search queries: One query per line, exactly as you would type it into YouTube search. YouTube operators such as intitle:"…" work. Duplicates are removed. Accounts on the Apify free plan can process up to 10 queries per run. |
| `maxResultsPerQuery` | integer | `20` | Max results per query: How many video results to return per query (1–200). Results are fetched in pages of about 20; each delivered result is one charged event, plus one page event per page. |
| `uploadDate` | string | `"any"` | Upload date: Only return videos uploaded within this period. One of `"any"`, `"today"`, `"this_week"`, `"this_month"`, `"this_year"`. |
| `sortBy` | string | `"relevance"` | Sort by: relevance (YouTube's default ranking) or popular (most viewed). One of `"relevance"`, `"popular"`. |
| `type` | string | `"all"` | Result type: all = videos, Shorts and live streams as YouTube mixes them; videos = regular videos only; shorts = Shorts only. One of `"all"`, `"videos"`, `"shorts"`. |
| `duration` | string | `"any"` | Duration: Filter regular videos by length (does not apply to Shorts). One of `"any"`, `"under_3_min"`, `"between_3_and_20_min"`, `"over_20_min"`. |
| `region` | string | — | Region: Optional 2-letter country code (e.g. US, GB, DE) to search as a viewer from that country. |
| `maxQueries` | integer | `100` | Max queries: Safety cap on the number of queries processed in this run. |
| `concurrency` | integer | `4` | Concurrency: How many queries are searched in parallel (1–8). |
| `itemTimeoutSecs` | integer | `600` | Time limit per input (seconds): Pagination for one query stops cleanly shortly before this limit (60–3600). Raise it for very large per-input limits. |
| `includeRaw` | boolean | `false` | Include raw upstream response: Attach the unmodified provider result object to each result record. |

Example input:

```json
{
  "queries": [
    "lofi hip hop"
  ],
  "maxResultsPerQuery": 10
}
```

## Output

One record per input item with `status` (`ok` is charged; `not_found`, `error`, `skipped_*` are free) and `charged`. The run stores an `OUTPUT` summary record.

| Field | Type | Meaning |
| --- | --- | --- |
| `input` | string | object | Input: The search query as provided. |
| `recordType` | string | Record type: result (one per video result, charged) \| query (one summary per query, never charged) |
| `query` | string | Query |
| `position` | integer | Position: 1-based position in the delivered results for the query (videos, then live, then Shorts within each page). |
| `videoId` | string | null | Video ID |
| `url` | string | null | URL |
| `type` | string | null | Type: video \| short \| live |
| `title` | string | null | Title |
| `description` | string | null | Description snippet: Usually null (YouTube search does not return descriptions). |
| `channelName` | string | null | Channel name |
| `channelId` | string | null | Channel ID |
| `channelHandle` | string | null | Channel handle |
| `channelUrl` | string | null | Channel URL |
| `publishedAt` | string | null | Published at: ISO 8601, approximate (derived from YouTube's relative time). |
| `publishedTimeText` | string | null | Published (relative): e.g. "2 years ago". |
| `durationSecs` | number | null | Duration (s) |
| `durationText` | string | null | Duration |
| `viewCount` | number | null | Views |
| `viewCountText` | string | null | Views (text) |
| `likeCount` | number | null | Likes: Usually null in search results. |
| `commentCount` | number | null | Comments: Usually null in search results. |
| `thumbnailUrl` | string | null | Thumbnail URL |
| `isShort` | boolean | null | Is Short |
| `isLive` | boolean | null | Is live |
| `badges` | array | Badges |
| `filters` | object | Filters: query records only: the filters applied. |
| `resultsDelivered` | integer | Results delivered: query records only. |
| `pagesFetched` | integer | Pages fetched: query records only. |
| `pagesCharged` | integer | Pages charged: query records only: number of youtube-search-query events. |
| `reachedEnd` | boolean | Reached end: query records only. |
| `stopReason` | string | Stop reason: query records only: limit \| end \| budget \| deadline \| max_pages \| empty_pages \| error |
| `status` | string | Status: ok \| not_found \| error \| skipped_invalid \| skipped_budget \| skipped_free_plan |
| `charged` | boolean | Charged: Result records: charged youtube-search-result. query summary records are never charged themselves: the youtube-search-query page fees charged for the input (including empty pages the provider billed) are counted in pagesCharged. |
| `chargedEvent` | string | Charged event |
| `error` | object | Error |
| `warnings` | array | Warnings |
| `source` | string | Source |
| `fetchedAt` | string | Fetched at |

Example record:

```json
{
  "input": "lofi hip hop",
  "recordType": "result",
  "query": "lofi hip hop",
  "position": 1,
  "videoId": "BzSzwqb-OEE",
  "url": "https://www.youtube.com/watch?v=BzSzwqb-OEE",
  "type": "video",
  "title": "NF - RUNNING (Audio)",
  "description": null,
  "channelName": "NFrealmusic",
  "channelId": "UCoRR6OLuIZ2-5VxtnQIaN2w",
  "channelHandle": null,
  "channelUrl": "https://www.youtube.com/channel/UCoRR6OLuIZ2-5VxtnQIaN2w",
  "publishedAt": "2023-05-28T17:08:46.499Z",
  "publishedTimeText": "2 years ago",
  "durationSecs": 254,
  "durationText": "4:14",
  "viewCount": 14860541,
  "viewCountText": "14,860,541 views",
  "likeCount": null,
  "commentCount": null,
  "thumbnailUrl": "https://i.ytimg.com/vi/BzSzwqb-OEE/hq720.jpg",
  "isShort": false,
  "isLive": false,
  "badges": [],
  "status": "ok",
  "charged": true,
  "chargedEvent": "youtube-search-result",
  "source": "scrapecreators",
  "fetchedAt": "2026-10-14T10:00:00.000Z"
}
```

## Related

- [YouTube Video Details Scraper](https://www.sfoln.com/actors/youtube-video-lookup.md) (`sfoln/youtube-video-lookup`)
- [YouTube Channel Videos Scraper](https://www.sfoln.com/actors/youtube-channel-videos.md) (`sfoln/youtube-channel-videos`)
- [YouTube Comments Scraper](https://www.sfoln.com/actors/youtube-video-comments.md) (`sfoln/youtube-video-comments`)
- [YouTube Video Summarizer – AI + Transcript](https://www.sfoln.com/actors/youtube-transcript-summary.md) (`sfoln/youtube-transcript-summary`)
- [TikTok Search Scraper – Videos by Keyword](https://www.sfoln.com/actors/tiktok-search.md) (`sfoln/tiktok-search`)

Publisher: SFOLN LLC, contact@sfoln.com. Catalogue: https://www.sfoln.com/catalog.json. Agent guide: https://www.sfoln.com/agents.md.
