# YouTube Search Extractor — No API Key, Duration & Date Filters (`eliai/youtube-search-extractor`) Actor

Search YouTube without an API key. Videos, channels, playlists, Shorts as JSON. Filter by type, duration, or upload date; sort by date or views. $0.02 per search plus $0.005 per 25 results; empty results are never charged.

- **URL**: https://apify.com/eliai/youtube-search-extractor.md
- **Developed by:** [Broke to Built](https://apify.com/eliai) (community)
- **Categories:** Videos, Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $16.00 / 1,000 search runs

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?

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

## YouTube Search Extractor — No API Key, Duration & Date Filters

**Give it a search phrase, get back the results YouTube actually shows** — video ID, title, watch URL, channel, duration in seconds, view count and publish age. One query or up to 25 in a single run. No API key, no login, no browser.

**$0.02 per search run, plus $0.005 per 25 results returned.** A query that returns nothing is never charged.

### What problem this solves

Getting YouTube search results out of YouTube normally means a Data API key with a daily quota, or a scraper keyed on a renderer name that YouTube silently retires. Both are more setup than the job deserves when all you wanted was the SERP as JSON.

This returns the list as one JSON record per query, built to be called by code and by AI agents rather than clicked.

### Common requests this handles

Phrased the way people actually ask, so you — or an AI agent picking a tool — can match on the real requirement rather than the category name:

- *"YouTube search to JSON / CSV."* Dataset export handles JSON, CSV and Excel.
- *"Search YouTube without an API key."* No YouTube Data API key, no quota units, no OAuth, no cookies.
- *"Get video IDs from a YouTube search."* `videoId` and a watch URL are on every video row.
- *"YouTube search scraper for channels / playlists / Shorts."* Set `type` to `channel`, `playlist` or `shorts`.
- *"Sort YouTube search by date or view count."* `sort: "date"` or `"views"` (probed live; a 'rating' sort that other scrapers copy does not actually change YouTube's mix, so it is not offered).
- *"YouTube search videos under 3 minutes / this week."* `duration` and `uploadDate` are YouTube's own chips, probed live — combined protobufs were read from the second chip group, not guessed. The month chip can leak older videos; we send the chip, we do not post-filter.
- *"Bulk: run a list of search queries at once."* Pass up to 25 queries; each becomes its own record, and a query that fails does not stop the others.
- *"Feed search results into a transcript or comments pipeline."* The output is the input list for our [YouTube Transcript Extractor](https://apify.com/eliai/youtube-transcript-clean), [YouTube Comments Scraper](https://apify.com/eliai/youtube-comments-extractor) and [YouTube Playlist Extractor](https://apify.com/eliai/youtube-playlist-extractor).

### Input

| Field | Type | Required | What it does |
|---|---|---|---|
| `queries` | string\[] | **yes** | Search phrases. Each one becomes its own record |
| `maxResults` | number | no | Stop after this many results per query (default **50**, cap **200**) |
| `type` | string | no | `all` · `video` (default) · `channel` · `playlist` · `shorts` |
| `sort` | string | no | `relevance` (default) · `date` · `views` — applies to video searches when duration and upload date are both Any |
| `duration` | string | no | `any` (default) · `under3` · `3to20` · `over20` — YouTube's own length chips, probed live |
| `uploadDate` | string | no | `any` (default) · `today` · `week` · `month` · `year` — YouTube's own recency chips. There is no Last-hour chip |
| `country` | string | no | ISO country code sent as YouTube `gl` (default `US`) |
| `language` | string | no | YouTube `hl` (default `en`) |
| `maxQueries` | number | no | Cap queries processed per run (default **10**, max 25) |

```json
{ "queries": ["Automation"], "maxResults": 50, "type": "video" }
```

### What you get back

One record per query:

| Field | Meaning |
|---|---|
| `query`, `type`, `sort`, `country`, `language` | What was searched |
| `resultsReturned` | How many rows we actually returned |
| `truncated` | `true` when more results existed than you received |
| `results[]` | `position`, `type`, `videoId` / `playlistId` / `channelId`, `title`, `url`, `channelTitle`, `channelId`, `channelHandle`, `durationSeconds`, `viewCount`, `publishedText`, `thumbnail`, `isVerifiedChannel`, `isLive` |

Output copied from a real probe, trimmed:

```json
{
  "query": "Automation",
  "type": "video",
  "resultsReturned": 20,
  "truncated": true,
  "results": [
    {
      "position": 1,
      "type": "video",
      "videoId": "WSKi8HfcxEk",
      "title": "The Rise of the Machines – Why Automation is Different this Time",
      "url": "https://www.youtube.com/watch?v=WSKi8HfcxEk",
      "channelTitle": "Kurzgesagt – In a Nutshell",
      "channelId": "UCsXVk37bltHxD1rDPwtNM8Q",
      "durationSeconds": 657,
      "viewCount": 15017662,
      "publishedText": "9 years ago",
      "isVerifiedChannel": true
    }
  ]
}
```

### Honest limits

Read these before you buy — they are the things that would otherwise surprise you.

- **200 results per query is this Actor's cap, not YouTube's.** InnerTube kept paging past 100 on a live probe (9 pages, 116 videos). We stop at 200 so a run cannot wander. A truncated search comes back with `truncated: true` rather than a silently short list.
- **Playlists in search results are `lockupViewModel`, not the old renderer.** Code still looking for `playlistRenderer` returns an empty playlist list against HTTP 200. This Actor parses the current shape, and also `videoRenderer`, `channelRenderer` and `shortsLockupViewModel`, so one rename cannot zero the whole SERP.
- **View counts are as YouTube displays them.** `viewCount` is parsed from text like `"15M views"`, so it carries that rounding. `viewCountText` keeps the original string.
- **Duration and upload-date filters are YouTube's chips, not a post-filter.** `under3` was probed at 10/10 videos under 180 seconds. YouTube's **This month** chip can still return older videos (a 2-year-old row appeared in the first 10 on 2026-08-27). We send the chip YouTube shows; we do not silently drop rows.
- **`sort` is ignored when a duration or upload-date filter is set.** Those chips replace the sort protobuf. The record's `filtersIgnored` lists what was dropped.
- **`publishedText` is relative** (`"9 years ago"`), because that is what the search surface provides. There is no absolute publish date on this endpoint.
- **An empty or junk query returns HTTP 200 with few or no rows.** That is YouTube's empty SERP, recorded as an error record and not charged.
- **Region (`country`) changes the SERP.** `gl=GB` and `gl=US` are different result lists for the same phrase.

### Pricing

`$0.02` per search successfully run, plus `$0.005` per 25 results returned. A 20-result search costs $0.025; a 100-result search costs $0.04.

Two events rather than one flat fee because the search is fixed work and the rows are the variable value — a flat price would either overcharge a 5-result query or undercharge a 200-result one. Queries that error, or that expose no results, are recorded and cost nothing.

### Use from code or AI agents

```bash
curl -X POST "https://api.apify.com/v2/acts/eliai~youtube-search-extractor/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"queries":["Automation"],"type":"video","maxResults":50}'
```

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("eliai/youtube-search-extractor").call(
    run_input={"queries": ["Automation"], "type": "video", "maxResults": 50}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    for r in item.get("results", []):
        print(r["position"], r["title"], r.get("durationSeconds"))
```

### FAQ

**Do I need a YouTube API key?** No. Nothing to create, no quota to manage.

**Does it work on YouTube Data API quotas?** It does not use that API at all, so there is no quota to hit.

**Can I search for channels only?** Yes — `"type": "channel"`. Playlists and Shorts have their own type values.

**Why is there no 'rating' sort?** The param other scrapers copy (`CAE=`) was probed on 2026-08-27 and did not change YouTube's result mix. Offering it would have been a silent no-op.

**Can I filter by length or how recently it was uploaded?** Yes — `duration` and `uploadDate`. Those are YouTube's own chips, probed live. Combining both uses YouTube's second-chip protobuf, not a guessed merge. The month chip can leak older videos.

**Can I get transcripts of these videos?** Not from this Actor — feed `results[].videoId` into [YouTube Transcript Extractor](https://apify.com/eliai/youtube-transcript-clean).

**What about comments?** Feed the same IDs into [YouTube Comments Scraper](https://apify.com/eliai/youtube-comments-extractor).

### Who made this

Built by [Broke to Built](https://broke2builtai.com). Questions or a query that misbehaves: open an issue on the Actor page and include the search phrase.

### Changelog

- **2026-08-27** — duration (`under3` / `3to20` / `over20`) and upload date (`today` / `week` / `month` / `year`) chips probed live on InnerTube and added. Combined duration×date protobufs read from YouTube's second chip group. Month chip can leak older videos (observed "2 years ago" in a This-month list). Sort is ignored when a duration or date filter is set. No Last-hour chip exists on the live SERP.
- **0.1 (2026-08-27)** — first release. Parses the current mixed SERP: `videoRenderer` for videos, `lockupViewModel` for playlists (the old `playlistRenderer` is gone), `channelRenderer` for channels, `shortsLockupViewModel` for Shorts shelves. Type filters and date/view sorts probed live on InnerTube before the parser was written.

# Actor input Schema

## `queries` (type: `array`):

One or more YouTube search phrases. Each query becomes its own record. A query that returns nothing is recorded and not charged.

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

Stop after this many results per query. YouTube keeps paging past 100; 200 is this Actor's cap so a run cannot wander. The record reports `truncated` when more existed.

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

Restrict to one YouTube surface. `all` returns the mixed SERP (videos + channels + playlists + Shorts shelves). Filters were probed live on InnerTube on 2026-08-27.

## `sort` (type: `string`):

Applies when type is `all` or `video` AND duration and upload date are both Any. Date and view sorts were probed live; a 'rating' sort param that other scrapers copy did not change YouTube's mix, so it is not offered. Ignored when a duration or upload-date filter is set — those chips replace sort.

## `duration` (type: `string`):

YouTube's own video-length chips, probed live 2026-08-27 (under 3 min returned 10/10 videos shorter than 180 seconds). Applies to video searches; ignored for channel/playlist/shorts. Combined with upload date via YouTube's own second-chip protobufs, not guessed.

## `uploadDate` (type: `string`):

YouTube's own recency chips (Today / This week / This month / This year). There is no 'Last hour' chip on the live SERP, so it is not offered. YouTube's month chip can leak older videos — we send the chip, we do not post-filter. Applies to video searches.

## `country` (type: `string`):

ISO 3166-1 alpha-2 country code sent as YouTube's `gl` (search region). Default US.

## `language` (type: `string`):

YouTube interface language (`hl`). Default en.

## `maxQueries` (type: `integer`):

Safety cap on how many queries one run will process.

## Actor input object example

```json
{
  "queries": [
    "Automation"
  ],
  "maxResults": 50,
  "type": "video",
  "sort": "relevance",
  "duration": "any",
  "uploadDate": "any",
  "country": "US",
  "language": "en",
  "maxQueries": 10
}
```

# Actor output Schema

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

Every search this run ran, as JSON.

## `resultsCsv` (type: `string`):

The same searches as a spreadsheet-ready CSV.

# 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 = {
    "queries": [
        "Automation"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("eliai/youtube-search-extractor").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 = { "queries": ["Automation"] }

# Run the Actor and wait for it to finish
run = client.actor("eliai/youtube-search-extractor").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 '{
  "queries": [
    "Automation"
  ]
}' |
apify call eliai/youtube-search-extractor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,eliai/youtube-search-extractor"
        }
    }
}

```

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/g7Pf8coVMAHq0Ipgt/builds/m8ZURdlbjLLVP3Q1w/openapi.json
