# Ad Creative Intelligence Scraper (`thenetaji/ad-creative-intelligence-scraper`) Actor

Search the Meta, Google, TikTok, and LinkedIn ad libraries for a competitor's ads, then take the videos apart: a word-timed transcript, the cut list, and a teardown of the hook, offer, CTA, and claims. Run the search on its own for a fast ad export, or analyse video URLs you already have.

- **URL**: https://apify.com/thenetaji/ad-creative-intelligence-scraper.md
- **Developed by:** [The Netaji](https://apify.com/thenetaji) (community)
- **Categories:** Videos
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.60 / 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/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

## Ad Creative Intelligence Scraper

The Actor searches the Meta, Google, TikTok, and LinkedIn public ad libraries for one advertiser's
ads and, on request, analyses each video rather than only cataloguing it: a transcript with
word-level timings, a scene list with per-shot motion, and a structured teardown of the hook, the
script's stages, the offer, the call to action, and the claims made.

Searching by advertiser name is the one operation every library supports, so `query` is the only
input most runs need.

### Three modes

`scraperType` chooses what a run does. The two are separate because they fail separately: a search
answers in seconds and has nothing in it that can time out, while an analysis downloads files and
reads them. Welding them together made every run only as reliable as the slowest, least certain
part of it.

The batch summary is not a third mode. It is a footer on the analysis run that produced it,
appended automatically as the last row.

**Find a competitor's ads** (`search`) — the ad libraries and nothing else. Returns each ad's
creative, copy, landing URL, media files, and dates. No downloads, no analysis. This is the mode
to start with, and the one that produces the video URLs the next mode takes.

**Take video ads apart** (`analyze`) — the transcript, the cuts, and the teardown. Give it a brand
name and it searches the libraries first, or paste video URLs into `videoUrls` and it analyses
exactly those and touches no ad library at all. The second shape is the dependable one: the videos
are already in hand, so no library can slow it down, refuse a country, or list an ad it publishes
no file for.

### Accepted input

- `scraperType` — `search` or `analyze`. Defaults to `search`.
- `query` — brand, company, or advertiser name. Required by `search`, and by `analyze` unless
  `videoUrls` is supplied.
- `platforms` — which libraries to search. **Defaults to Meta only**, which is the library that
  serves this reliably worldwide. Add the others deliberately: Google covers one country per run,
  TikTok covers only Europe (below), and LinkedIn publishes a media file for only some of its ads.
- `countries` — comma-separated two-letter codes, default `US`. Meta and Google read the first
  entry; LinkedIn reads the whole list. `UK` is corrected to `GB` and `EL` to `GR`.
- `mediaType` — `all`, `video`, or `image`, for `search`. The analysis modes always narrow to
  video, because an image ad has no transcript and no cuts.
- `date_min` and `date_max` — optional activity-date bounds. The TikTok Ads Library requires a
  window and searches the last 90 days when both are blank.
- `active_status` — `all`, `active`, or `inactive`. Only the Meta Ad Library filters on delivery
  status upstream.
- `videoUrls` — for `analyze`: analyse exactly these videos and search no library. Paste the
  `video_url` column straight from a search run. When this is set, `query` and the library
  settings are ignored, and the run log says so.
- `maxAds` — how many ads to return or analyse; default `10`, maximum `50`.
- `transcribeCreatives` — default `true`.
- `detectScenes` — default `true`.
- `teardownCreatives` — default `true`.
- `batchReport` — default `true`. Appends the summary row to the end of an `analyze` run. Billed
  once, and only when the row is actually produced.
- `includeTranscriptText` — default `true`. Disabling it drops the full text, segments, and word
  timings from each row and retains the teardown and the scene list.

### Response fields

Two record shapes are saved, distinguished by `record_type`.

A `search` run saves `record_type: "ad"` rows carrying the ad-library fields only: `platform`,
`ad_id`, `ad_url`, `advertiser_name`, `advertiser_id`, `first_seen`, `last_seen`, `is_active`, and
`creative`, plus `video_url` — the one file worth analysing, already picked out of the creative's
mixed media list so it can be pasted straight into the analysis mode. The analysis fields are
absent rather than present and empty.

From an `analyze` run, a record with `record_type: "ad"` is one analysed ad. It carries the same ad-library fields `platform`,
`ad_id`, `ad_url`, `advertiser_name`, `advertiser_id`, `first_seen`, `last_seen`, `is_active`, and
`creative`, followed by the analysis:

- `media` — duration, dimensions, and whether the file carried an audio track.
- `hook` — the words spoken in the first three seconds, taken from the transcript's word timings
  rather than from the first sentence, so a sentence crossing the boundary is cut where the viewer
  heard it cut. It is read from the transcript rather than from the teardown, so it is populated on
  a run with `teardownCreatives` disabled.
- `transcript` — `language`, `has_speech`, `word_count`, and, unless `includeTranscriptText` is
  disabled, `text`, `segments`, and `words`.
- `scenes` — `count`, `cuts_per_minute`, and each scene's start, end, and motion, classified as
  `static`, `moderate`, or `dynamic`.
- `teardown` — `hook`, `script_structure`, `offer`, `cta`, `claims`, `language`, and `confidence`.
  Each claim records the evidence it was drawn from.
- `analysis` — the outcome of each step, as `ok`, `skipped`, or `failed`, with a reason.

A record with `record_type: "batch_report"` is written once, last. Its `report` field counts what
recurs across the ads analysed in the run: `recurring_hooks`, `hook_types`, `recurring_offers`,
`discounts`, `cta_actions`, `cta_wording`, `recurring_claims`, `claim_types`, `languages`, and
`pacing`, the last holding the median duration, median scene count, and median cuts per minute.
`stages_failed` counts the ads on which each step did not complete, so a thin report is
explainable without repeating the run. This row is the reason to analyse twenty ads rather than
one: a pattern across an advertiser's slate is not visible one ad at a time.

Fields absent from the upstream response are returned as null rather than omitted.

#### Example input

A plain search:

```json
{
  "scraperType": "search",
  "query": "athletic greens",
  "countries": "US",
  "maxAds": 20
}
```

Analysing what that search found, with no library in the path:

```json
{
  "scraperType": "analyze",
  "videoUrls": ["https://video.xx.fbcdn.net/v/t42.1790-2/example.mp4"]
}
```

#### Example record

```json
{
  "record_type": "ad",
  "platform": "meta",
  "ad_id": "527778036512305",
  "ad_url": "https://www.facebook.com/ads/library/?id=527778036512305",
  "advertiser_name": "Nike",
  "media": { "duration_seconds": 31.4, "width": 1080, "height": 1920, "has_audio": true },
  "hook": "Three seconds is all anyone gives an ad, so this one starts here.",
  "transcript": { "language": "en", "has_speech": true, "word_count": 96 },
  "scenes": { "count": 14, "cuts_per_minute": 26.8 },
  "teardown": {
    "hook": { "text": "Three seconds is all anyone gives an ad, so this one starts here.", "type": "pattern_interrupt" },
    "offer": { "present": true, "description": "20% off first order", "discount": "20%" },
    "cta": { "present": true, "action": "shop", "text": "Shop the new collection" },
    "claims": [{ "text": "Lasts twice as long", "type": "comparative", "source": "transcript" }]
  },
  "analysis": {
    "transcribe": { "status": "ok" },
    "scenes": { "status": "ok" },
    "teardown": { "status": "ok" }
  }
}
```

### What each library actually covers

The four libraries are not interchangeable, and the default input does not reach all of them.

The **TikTok Ads Library** is TikTok's EU Commercial Content Library. It carries only ads delivered
in the EEA, Switzerland, the United Kingdom and Turkey. Selecting it alongside countries it does
not cover leaves it out of the search before the run starts, with a line in the log saying why —
the coverage is set by the regulation the library exists to satisfy, not by the advertiser being
searched for, so asking it anyway can only waste the call. A run that selects TikTok *and nothing
else* for a country outside its coverage is refused outright, because an empty result there would
read as "this advertiser has no ads" when the truth is that the library was never able to answer.

The **Meta Ad Library** publishes an ad's `format` from the ad's own metadata and its media files
separately, so an ad marked `video` does not always carry a downloadable file. Ads listed as video
with no file are passed over before any analysis and are not charged for; the run log counts them.
A search that returns twenty listings can therefore analyse fewer than twenty ads, and the Actor
over-fetches to absorb this.

Only the **Meta Ad Library** filters on delivery status, which is why `active_status` changes Meta
results and leaves the others unaffected.

### Video length limit

Creatives longer than two minutes are refused, and the ad is saved with its remaining fields and
the reason recorded on `analysis`. The limit is measured rather than chosen: transcription of a
spoken-word creative costs approximately half a second of processing per second of video, so a
five-minute creative would take longer to transcribe than a single request is permitted to run.
Ad creatives are rarely that long; one that is returns a record with a reason attached rather
than failing the run.

### Charged events

Each step is charged only when it produces a result.

- Transcription is charged per minute of video, rounded up, and only once a transcript has been
  returned. A creative that could not be downloaded, was refused as too long, or proved not to be
  a video is not charged.
- The teardown is charged once per teardown returned.
- The batch report is charged once per run, and only when the summary row is produced.

Disabling a step in the input removes its charge entirely. A step that fails is not charged, and
the record is retained with the fields the other steps produced.

### Behaviour on partial results

A single ad that fails is retained in the dataset with its remaining fields and a reason on
`analysis`. An expired media link on one ad among forty is ordinary and does not interrupt the run.

A step that fails for a reason which will hold for every remaining ad is switched off for the rest
of the run after the first occurrence, recorded once in the log, and marked `skipped` with that
reason on every later record. Repeating it per ad costs a request each and cannot succeed.

A library that is unavailable does not fail the run. The run completes with results from the
libraries that answered, and the run log names the one that did not. The run fails only when every
selected library is unavailable.

An interrupted run resumes at the exact ad it stopped on and neither re-analyses nor re-charges
the ads already saved. Because a resumed run cannot read the records its earlier attempt pushed,
its summary row is marked `covers_this_attempt_only` rather than under-counting silently.

### Creatives without speech

A creative carrying a backing track and no voice-over returns an empty transcript with
`has_speech` set to `false`, and `analysis.transcribe` reads `ok`. Silence is a finding rather than
a failure; a fast-cut creative with no narration is a deliberate format, and the scene list still
describes it. The teardown then runs on the published ad copy where the library publishes any.
Where there is neither speech nor copy, the teardown is skipped and not charged rather than
produced from nothing.

### Scope

Only what the ad libraries publish to logged-out visitors is read. The Actor holds no account on
any platform and reaches no signed-in surface, so material a library shows only to authenticated
users is outside its scope.

Spend and impression brackets are not analysed here. Where the ad's metadata is sufficient and the
creative itself is not required, the
[All-in-One Ad Library Scraper](https://apify.com/thenetaji/all-in-one-ad-library-scraper) covers
the same four libraries at a lower cost per ad.

### Related Actors

- [All-in-One Ad Library Scraper](https://apify.com/thenetaji/all-in-one-ad-library-scraper) — the
  same four libraries, metadata only, without creative analysis.
- [Meta Ad Library API Scraper](https://apify.com/thenetaji/meta-ad-library-api-scraper) — Facebook
  and Instagram ads, with platform, language, and reach filters.
- [TikTok Ads Library Scraper](https://apify.com/thenetaji/tiktok-ads-library-scraper) — TikTok ads
  with targeting detail and advertiser activity reports.

# Actor input Schema

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

Choose the dataset for this run, then fill in the section for that mode below.

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

Brand, company, or advertiser name whose video ads you want taken apart. Every library supports advertiser-name search, so this one input works across all four.

## `platforms` (type: `array`):

Which public ad libraries to search. Meta is on by default because it is the one that serves this reliably worldwide; add the others deliberately. Google covers one country per run, TikTok covers only the EEA, Switzerland, the UK and Turkey, and LinkedIn publishes a file for only some of its ads.

## `maxAds` (type: `integer`):

In search mode, how many ads to return. In an analysis mode, how many videos to take apart — each one is downloaded, transcribed, cut-detected, and read, which is far more work per row than a plain ad export, so start small there and raise it once you have seen the output.

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

Which formats to return. Only video ads can be transcribed or cut-detected, so the analysis modes always narrow to video regardless of this setting; it is here for a plain search, where an image ad's copy and landing page are perfectly readable.

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

Two-letter country codes, comma-separated (for example US,GB). Meta and Google use the first entry and LinkedIn uses the whole list. TikTok is the exception: its library covers only the EEA, Switzerland, the UK and Turkey, so a US search skips TikTok and says so.

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

Earliest ad-activity date. Optional. Leave blank and the TikTok Ads Library, which requires a window, falls back to the last 90 days.

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

Latest ad-activity date. Optional; defaults to today.

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

Only Meta's ad library can filter by whether an ad is still running. The other libraries ignore this and use the activity dates above.

## `videoUrls` (type: `array`):

Analyse exactly these videos and search no ad library at all. Paste the media URLs a search run returned (each ad's creative.media\_urls). This is the mode that cannot be affected by a library being slow, refusing a country, or publishing an ad with no file — the videos are already in hand.

## `transcribeCreatives` (type: `boolean`):

Turn each video's speech into text with word-level timings. This is what makes the first three seconds quotable, and the teardown below has almost nothing to work from without it.

## `detectScenes` (type: `boolean`):

Find every scene change and how much moves inside each shot, so you can see the ad's pacing rather than guess it.

## `teardownCreatives` (type: `boolean`):

Turn the transcript, the cuts, and the published copy into a structured teardown: the hook, the script's stages, the offer, the call to action, and the claims made.

## `batchReport` (type: `boolean`):

Appends one final row that counts the hooks, offers, and calls to action that recur across every ad analysed in this run — the patterns you cannot see one ad at a time. Billed once per run, and only when the row is actually produced.

## `includeTranscriptText` (type: `boolean`):

Keep each ad's full transcript and word timings in its row. Turn off for a much smaller dataset that still carries the teardown and the scene list.

## Actor input object example

```json
{
  "scraperType": "search",
  "query": "nike",
  "platforms": [
    "meta"
  ],
  "maxAds": 10,
  "mediaType": "all",
  "countries": "US,GB",
  "date_min": "2026-05-01",
  "date_max": "2026-08-01",
  "active_status": "all",
  "videoUrls": [
    "https://video.xx.fbcdn.net/v/t42.1790-2/example.mp4"
  ],
  "transcribeCreatives": true,
  "detectScenes": true,
  "teardownCreatives": true,
  "batchReport": true,
  "includeTranscriptText": true
}
```

# Actor output Schema

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

All records scraped by this run

# 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 = {
    "scraperType": "search",
    "query": "nike",
    "platforms": [
        "meta"
    ],
    "maxAds": 10,
    "mediaType": "all",
    "countries": "US",
    "active_status": "all"
};

// Run the Actor and wait for it to finish
const run = await client.actor("thenetaji/ad-creative-intelligence-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 = {
    "scraperType": "search",
    "query": "nike",
    "platforms": ["meta"],
    "maxAds": 10,
    "mediaType": "all",
    "countries": "US",
    "active_status": "all",
}

# Run the Actor and wait for it to finish
run = client.actor("thenetaji/ad-creative-intelligence-scraper").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 '{
  "scraperType": "search",
  "query": "nike",
  "platforms": [
    "meta"
  ],
  "maxAds": 10,
  "mediaType": "all",
  "countries": "US",
  "active_status": "all"
}' |
apify call thenetaji/ad-creative-intelligence-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,thenetaji/ad-creative-intelligence-scraper"
        }
    }
}

```

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/l6qphgpEEgtKkshAa/builds/XhV2va0NE4oysliZF/openapi.json
