# Ad Library Intelligence (Google, LinkedIn, TikTok) (`almoutasem_nabil/ad-library-intelligence`) Actor

Monitor competitors' ads across the Google Ads Transparency Center, LinkedIn Ad Library and TikTok Creative Center. Normalized ad records plus change events: new ads, ended ads, creative refreshes, new regions and new landing domains.

- **URL**: https://apify.com/almoutasem\_nabil/ad-library-intelligence.md
- **Developed by:** [Al Moutasem Nabil](https://apify.com/almoutasem_nabil) (community)
- **Categories:**
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 ad records

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

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 Library Intelligence: Google, LinkedIn and TikTok ad monitoring

**Give it a list of competitors and get their currently running ads from the Google Ads Transparency Center, the LinkedIn Ad Library and TikTok's Creative Center, normalized into one schema. Schedule it and get only what changed: new ads, ended ads, creative refreshes, new landing domains and new regions.**

Meta's ad library has a dozen actors. Google, LinkedIn and TikTok have one to three each, none of them combined, and none of them tell you what changed since yesterday. This Actor does both.

### Who is it for

- **Agencies and performance marketers** who need a weekly competitor creative review without opening three transparency sites.
- **Brand and product marketing teams** tracking when a competitor launches a campaign, refreshes creatives or starts advertising in a new country.
- **Competitive intelligence and sales teams** watching landing-domain changes (a new product microsite is usually a launch signal).
- **Founders and analysts** sizing up how heavily a company advertises and where.

### What makes it different

- **Three libraries in one run**, one schema per ad (`platform`, `advertiser`, `format`, copy, `landingDomain`, `mediaUrls`, dates, `regions`).
- **Change detection.** A compact snapshot per watch list is kept in the `ad-intel-state` key-value store; every run emits `new_ad`, `ad_ended`, `creative_refresh`, `new_landing_domain`, `new_region` and `advertiser_error` events. Ended ads stay in the snapshot so each event fires once; ads whose library id changed but whose creative did not are recognised as the same ad.
- **Internal JSON endpoints where they are stable.** Google's own transparency RPCs (`SearchCreatives`, `GetCreativeById`) and TikTok's signed `top_ads` API are used directly; LinkedIn's server-rendered pages are parsed with text-anchored selectors. No login anywhere, no cookies, no media downloads.
- **LinkedIn impression ranges and country shares**, **TikTok engagement metrics** (likes, comments, shares, CTR percentile, duration), **Google first/last shown dates and regions**.
- **Advertiser disambiguation.** A name search on Google can match ten advertisers ("Notion OU", "NOTION LLC", "Skin notion"); the resolver scores them by name and domain, and the run summary lists the candidates so you can pin `googleAdvertiserId` next time.
- **Per advertiser and per platform isolation.** A 403 on LinkedIn does not touch the Google results of the same advertiser, and in change mode it never produces `ad_ended` events; you get one `advertiser_error` with the previous count instead.
- **No personal data.** Ads run by individual people rather than organisations are skipped, and phone numbers or emails inside ad copy are masked.

### Input

| Field                 | Type    | Default                 | Description                                                                                                                        |
| --------------------- | ------- | ----------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `advertisers`         | array   | –                       | `{ name, domain, googleAdvertiserId, linkedinCompanyId or linkedinUrl }`. Name alone works; ids remove ambiguity.                  |
| `platforms`           | array   | `["google","linkedin"]` | Any of `google`, `linkedin`, `tiktok`.                                                                                             |
| `googleRegion`        | string  | `anywhere`              | ISO-2 country code to filter Google creatives.                                                                                     |
| `linkedinCountries`   | array   | –                       | ISO-2 codes; limits the per-country impression breakdown.                                                                          |
| `tiktokQueries`       | array   | –                       | `{ keyword, industry, region (default SA), objective, period (7/30/180), adFormat }`. TikTok is query-based, not advertiser-based. |
| `activeOnly`          | boolean | `true`                  | Keep only currently running ads.                                                                                                   |
| `lastNDays`           | integer | `90`                    | Drop ads last shown before this window.                                                                                            |
| `maxAdsPerAdvertiser` | integer | `200`                   | Cap per advertiser and platform.                                                                                                   |
| `emitChangesOnly`     | boolean | `false`                 | Change events instead of ads. Needs `trackingId`.                                                                                  |
| `emitBaselineAsNew`   | boolean | `false`                 | First run for a `trackingId` emits every ad as `new_ad` instead of a silent baseline.                                              |
| `trackingId`          | string  | –                       | Name of the watch list (state key).                                                                                                |
| `refreshThreshold`    | integer | `5`                     | New ads from one advertiser on one platform in one run that count as `creative_refresh`.                                           |
| `maxConcurrency`      | integer | `5`                     | Advertiser × platform sources in parallel.                                                                                         |
| `proxyConfiguration`  | object  | Apify datacenter        | See proxy guidance below.                                                                                                          |

**Snapshot mode** (every running ad, once):

```json
{
    "advertisers": [
        {
            "name": "HubSpot",
            "domain": "hubspot.com",
            "googleAdvertiserId": "AR10072600183532683265",
            "linkedinUrl": "https://www.linkedin.com/company/hubspot"
        },
        { "name": "Notion", "domain": "notion.so" },
        { "name": "monday.com", "domain": "monday.com" }
    ],
    "platforms": ["google", "linkedin", "tiktok"],
    "tiktokQueries": [{ "keyword": "project management", "region": "US", "period": 30 }],
    "maxAdsPerAdvertiser": 100
}
```

**Change mode** (schedule daily):

```json
{
    "advertisers": [
        { "name": "HubSpot", "domain": "hubspot.com" },
        { "name": "Notion", "domain": "notion.so" }
    ],
    "platforms": ["google", "linkedin"],
    "trackingId": "competitor-ads",
    "emitChangesOnly": true,
    "refreshThreshold": 5
}
```

### Output

Snapshot mode pushes one record per ad (see `.actor/dataset_schema.json` for every field). Google and LinkedIn ads use the same shape with `linkedin.impressionsRange` / `linkedin.countryBreakdown` filled for LinkedIn and `firstShownAt` / `lastShownAt` for Google. A TikTok top ad from a test run (query `fitness app`, US):

```json
{
    "platform": "tiktok",
    "advertiser": {
        "name": "celunes",
        "id": null,
        "domain": null,
        "verified": null
    },
    "adId": "7660514428254257153",
    "libraryUrl": "https://ads.tiktok.com/business/creativecenter/topads/7660514428254257153/pc/en",
    "format": "video",
    "headline": "Fast heat, steady comfort, zero cords. Keep one in your bag for your next period.",
    "bodyText": null,
    "cta": null,
    "landingUrl": "https://www.celunes.com/products/celunes-menstrual-heating-patch?utm_source=tiktok&utm_id=__CAMPAIGN_ID__&utm_campaign=__CAMPAIGN_NAME__&utm_medium=paid&tt_campaignid=__CAMPAIGN_ID__",
    "mediaUrls": [
        "https://p16-common-sign.tiktokcdn.com/tos-alisg-p-0051c001-sg/oAVEUILIgA9ABD5uNFf1iqDBd2UnkEDf0niHAS~tplv-noop.image?dr=18692&refresh_token=0f4f54b1&x-expires=1788665814&x-signature=OQEOAwkGYB%2BiaLGib4KA0AQh4%2Bk%3D&t=9276707c&ps=14f1eb3e&shp=9e36835a&shcp=317596d8&idc=my2&VideoID=v14033g50000d992uhvog65sgum5vei0",
        "https://v16m-default.tiktokcdn.com/0b7a77f2eefcb8a6d8fda8147d9575bd/6a9cdfd6/video/tos/alisg/tos-alisg-ve-0051c001-sg/oUQcaY1Pm1ixtuASAgBPldie4XSJwA0IKJnBEB/?a=0&bti=NTU4QDM1NGA%3D&&bt=1137&ft=cApXJCz7ThWHo1~5LGZmo0P&mime_type=video_mp4&rc=PDM6NDQ3ZTQ0ZTU0ZTllOEBpM2g4eHY5cms1PDMzODYzNEA1Y18vYy0tNWIxL141NV4zYSNmc2pkMmRzcjZhLS1kMDFzcw%3D%3D&vvpl=1&l=202609060536404018C6138272D616A929&btag=e000b0000"
    ],
    "firstShownAt": null,
    "lastShownAt": null,
    "isActive": true,
    "regions": ["AU", "CA", "US"],
    "tiktok": {
        "likes": 2,
        "comments": 0,
        "shares": 0,
        "ctrPercentile": 0.8,
        "durationSec": 13.584,
        "industry": "label_14107000000",
        "objective": "conversion",
        "region": "US",
        "query": "tiktok \"fitness app\" · US · 30d"
    },
    "scrapedAt": "2026-09-05T21:36:31.918Z",
    "landingDomain": "celunes.com"
}
```

Change mode pushes one item per change event (`type`: `new_ad`, `ad_ended`, `creative_refresh`, `new_landing_domain`, `new_region`, `advertiser_error`). Real items from a test run:
`creative_refresh`:

```json
{
    "type": "creative_refresh",
    "platform": "tiktok",
    "advertiser": "tiktok SA · 30d",
    "sourceKey": "tiktok:||sa||30|",
    "trackingId": "proof-tiktok",
    "observedAt": "2026-09-05T21:26:46.551Z",
    "count": 6,
    "threshold": 5,
    "headlines": [
        "#متخصص في دهان الابواب وتجديد بويه الابواب القديمه دهان طاولات ومكتبات تجديد بويه المطابخ دهان غرف النوم الخشب دهان الديكورات الخشبيه والحديد دهان كراسي متواجد بمكه, [phone]",
        "مضغوط نثري موقعهم حي الرمال على طريق وادي الساحل طبعاً الدجاج واللحم مطبوخ بالبخار لمدة 6 ساعات بدون نقطة مويه 🤯👌🏻 التوصيل مجاني لحي الرمال 🚗 Ad #الرمال #مضغوط #مضغوط_ نثري #مدفون #رز",
        "الثانية مجاناً لايفوتك",
        "❌ لا تغيّر بابك! خلّيه يرجع جديد 👌 بلون فخم وتشطيب يشرّف 🔥 بابك باهت؟ فيه خدوش؟ أثر شمس أو صدأ؟ الحل أسهل مما تتوقع ✨ نقدّم صبغ احترافي لأبواب الحديد والخشب دهانات أصلية 🎨 ثبات لون عالي وحماية من الحرارة والرطوبة 💪 شغل احترافى 👌 تنفيذ سريع ⏱️ ونسلّم في الوقت ✔️ من باب عادي… إلى باب يغيّر شكل المكان بالكامل 😍 تبغى بابك كأنه جديد؟ تواصل معنا الآن 📲 وخلي الصبغ علينا ✨ معاينة مجانية #ص#صبغ_ابوابص#صبغ_ابواب_حديد_وخشب_طاولات_دواليبص#صبغ_خشبد#ديكورتشطيبات_داخلية_خارجية",
        "Let every performance take you deeper into the island’s culture",
        "باقه جاك سبارو من ريف #ريف #عطورات #عطور_ريف : : @ReefPerfumes : : : : :"
    ]
}
```

`new_ad`:

```json
{
    "type": "new_ad",
    "platform": "tiktok",
    "advertiser": "tiktok SA · 30d",
    "sourceKey": "tiktok:||sa||30|",
    "trackingId": "proof-tiktok",
    "observedAt": "2026-09-05T21:26:46.551Z",
    "ad": {
        "platform": "tiktok",
        "advertiser": {
            "name": "Unnamed advertiser",
            "id": null,
            "domain": null,
            "verified": null
        },
        "adId": "7663836490589798420",
        "libraryUrl": "https://ads.tiktok.com/business/creativecenter/topads/7663836490589798420/pc/en",
        "format": "video",
        "headline": "#متخصص في دهان الابواب وتجديد بويه الابواب القديمه دهان طاولات ومكتبات تجديد بويه المطابخ دهان غرف النوم الخشب دهان الديكورات الخشبيه والحديد دهان كراسي متواجد بمكه, [phone]",
        "bodyText": null,
        "cta": null,
        "landingUrl": null,
        "mediaUrls": [
            "https://p16-common-sign.tiktokcdn.com/tos-alisg-p-0037/okYwYCuB1Am4ELEiiJ4EiAIaCAnYfLowTIIPBy~tplv-noop.image?dr=18692&refresh_token=9dd838b4&x-expires=1788665231&x-signature=jmGEMTeD%2BkrDXpOxaf38DsflHCY%3D&t=9276707c&ps=14f1eb3e&shp=9e36835a&shcp=317596d8&idc=my2&VideoID=v1c044g50000d9c9gpfog65od4ss84c0",
            "https://v16m-default.tiktokcdn.com/9edd8322f75a23c75ae637e5b75ad7ab/6a9cdd8f/video/tos/alisg/tos-alisg-pve-0037c001/o8BTui4OwEmLyiOCEuEfZnFQ4EuYAIwaY1IEAA/?a=0&bti=NTU4QDM1NGA%3D&&bt=361&ft=cApXJCz7ThWH2f~5LGZmo0P&mime_type=video_mp4&rc=NmRpNDtmNjU7NDtpZDM0aEBpMzd2N3I5cnM8PDMzODczNEBeX2FfYl8yXi8xMV9eYzBgYSNgNXBhMmRjZGBhLS1kMWBzcw%3D%3D&vvpl=1&l=202609060526513B1EE5585E84C405B6AB&btag=e000b8000"
        ],
        "firstShownAt": null,
        "lastShownAt": null,
        "isActive": true,
        "regions": ["SA"],
        "tiktok": {
            "likes": 44,
            "comments": 1,
            "shares": 1,
            "ctrPercentile": 0.7,
            "durationSec": 20.875,
            "industry": "label_21100000000",
            "objective": "conversion",
            "region": "SA",
            "query": "tiktok SA · 30d"
        },
        "scrapedAt": "2026-09-05T21:26:46.551Z",
        "landingDomain": null
    }
}
```

`ad_ended`:

```json
{
    "type": "ad_ended",
    "platform": "tiktok",
    "advertiser": "tiktok SA · 30d",
    "sourceKey": "tiktok:||sa||30|",
    "trackingId": "proof-tiktok",
    "observedAt": "2026-09-05T21:26:46.551Z",
    "ad": {
        "hash": "ghost",
        "firstSeenAt": "2026-09-05T21:25:53.824Z",
        "landingDomain": "ghost.example",
        "regions": ["ZZ"],
        "isActive": true,
        "headline": "Your next trip deserves a first-class upgrade 🚐 Experience the ultimate comfort in our premium Toyota Alphard with a professional chauffeur. ✨ Captain electric seats 📺 On-board TV, Wi-Fi, & chargers ☀️ Electric sunshades & sunroof 🧳 Perfect for business trips, family vacations, & airport transfers Ready to elevate your travel from Bangkok to Pattaya, Hua Hin, Khao Yai, and beyond? Contact us to reserve your ride! 📲 #toyotaalphard #luxurytravel #thailandtravel #bangkoktransport #privatedriver",
        "libraryUrl": "https://ads.tiktok.com/business/creativecenter/topads/7667040007445889025/pc/en",
        "adId": "GHOST-ENDED"
    }
}
```

`new_landing_domain`:

```json
{
    "type": "new_landing_domain",
    "platform": "tiktok",
    "advertiser": "tiktok SA · 30d",
    "sourceKey": "tiktok:||sa||30|",
    "trackingId": "proof-tiktok",
    "observedAt": "2026-09-05T21:26:46.551Z",
    "domain": "linktr.ee",
    "count": 1,
    "headlines": [
        "مضغوط نثري موقعهم حي الرمال على طريق وادي الساحل طبعاً الدجاج واللحم مطبوخ بالبخار لمدة 6 ساعات بدون نقطة مويه 🤯👌🏻 التوصيل مجاني لحي الرمال 🚗 Ad #الرمال #مضغوط #مضغوط_ نثري #مدفون #رز"
    ]
}
```

The key-value store record `SUMMARY` lists every advertiser × platform with ads found, active ads, requests made, duration, proxy type, resolved advertiser and disambiguation candidates, plus charged events per run.

### Scheduling and notifications

1. Fill in the change-mode input and **Save as task**.
2. Add a **Schedule** to the task (daily at 07:00 is typical; TikTok periods are 7/30/180 days, so daily runs are fine).
3. Under **Integrations** on the task add **Slack**, **Email**, **Webhook**, **Make** or **Zapier** on *Run succeeded*. In change mode the dataset holds only the change events, so an empty dataset means nothing changed.
4. From your own code: `POST https://api.apify.com/v2/acts/<username>~ad-library-intelligence/run-sync-get-dataset-items?token=<token>` with the change-mode JSON.

State lives in the named key-value store `ad-intel-state` in your account, one record per `trackingId`; delete it to reset a watch list.

### Pricing

Pay-per-event. You pay for ads and signals, never for requests, retries, detection or failed sources.

| Event              | Price  | When                                                              |
| ------------------ | ------ | ----------------------------------------------------------------- |
| Run started        | $0.01  | Once per run.                                                     |
| Advertiser checked | $0.005 | Per advertiser × platform (or TikTok query) fetched successfully. |
| Ad record          | $0.003 | Snapshot mode, per Google or LinkedIn ad stored.                  |
| Ad record (TikTok) | $0.005 | Snapshot mode, per TikTok top ad stored (browser compute).        |
| Change detected    | $0.01  | Change mode, per change event stored. `advertiser_error` is free. |

Worked example, **20 competitors on Google and LinkedIn, daily change mode**, about 15 changes a day: $0.01 + 40 × $0.005 + 15 × $0.01 = **$0.36 per run, about $11 per month**. A weekly full snapshot of the same 20 competitors with 1,500 running ads costs about $4.71 per run. Platform compute is billed separately by your Apify plan; a typical change run takes one to three minutes.

### Platform notes and limitations

- **Google**: dates are shown as ranges (`firstShownAt`, `lastShownAt`) and "active" means shown within the last 7 days. Google exposes creatives as image previews or preview scripts, not as headline/body text, so `headline`, `bodyText`, `cta` and `landingUrl` are `null` for Google ads; `mediaUrls` and `regions` are complete. Text ads are rendered as images by Google. Regions cost one extra RPC per new ad; ads already in the snapshot reuse their stored regions.
- **LinkedIn**: the Ad Library sits behind Cloudflare and rejects a share of datacenter requests with 403. The Actor retries with spaced attempts and alternating HTTP versions; if a source still fails, switch `proxyConfiguration` to **residential** proxies. Name searches return every advertiser mentioning the word; the Actor keeps only the matching organisation, so pass `linkedinCompanyId` (numeric) when you have it. Impressions are ranges ("< 1k", "1k-5k"); country shares appear only when LinkedIn shows them.
- **TikTok**: there is no advertiser search, so the unit is a query (keyword / industry / objective / region / period) and change mode means "new top ads for this query". Without a login TikTok serves the top 20 ads per query; use several queries for coverage. Keyword queries run through the page's search box; other filters call the signed API directly. `firstShownAt`/`lastShownAt` are not exposed by TikTok.
- Change detection is relative to the filtered set (`activeOnly`, `lastNDays`, `googleRegion`); changing them between runs produces events.
- `mediaUrls` are the libraries' own CDN links (Google `tpc.googlesyndication.com`, LinkedIn `media.licdn.com`, TikTok CDN) and can expire; nothing is downloaded or rehosted.

### Proxy guidance

- **Google and TikTok**: Apify datacenter proxies are enough. Google rate-limits bursts per IP; the Actor paces its RPCs and backs off on 429.
- **LinkedIn**: start with datacenter; if the summary shows repeated 403s on LinkedIn sources, set `proxyConfiguration` to `{ "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }`. Residential traffic costs more, so keep `maxAdsPerAdvertiser` moderate there.

### Related

**[Reviews Scraper & AI Insights](../review-intelligence)** collects public reviews for a company or app across five sources, with AI themes and negative-review alerts.

Watching a specific page rather than a feed? **[Website Change Monitor](../page-change-monitor)** reports exactly what changed on any URL, with a diff and an optional AI summary.

### Legal note

The Actor reads public advertising transparency data that the platforms publish for exactly this purpose. It never logs in, sends no cookies, paces its requests, links to creatives instead of copying them, and stores no personal data (ads by individual people are skipped; contact details in copy are masked). Brand names appear factually to identify advertisers. Use the data in line with each platform's terms and the laws that apply to you. Google, LinkedIn and TikTok are trademarks of their owners; this Actor is not affiliated with them.

# Actor input Schema

## `advertisers` (type: `array`):

Advertisers to check on Google and LinkedIn: { name, domain, googleAdvertiserId (AR…), linkedinCompanyId or linkedinUrl }. Name alone works; add the domain or ids to avoid ambiguity (the run summary lists candidates when a name is ambiguous).

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

Which libraries to read. TikTok is query-based (see tiktokQueries) and uses a headless browser. LinkedIn's Ad Library sits behind Cloudflare and rejects Apify datacenter IPs, so add "linkedin" together with residential proxies in "proxyConfiguration"; "tiktok" needs at least one entry in "tiktokQueries".

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

Region filter for the Google Ads Transparency Center: 'anywhere' or an ISO-2 country code (US, AE, SA, GB...).

## `linkedinCountries` (type: `array`):

ISO-2 codes. When set, the per-country impression breakdown is limited to these countries.

## `tiktokQueries` (type: `array`):

TikTok has no per-advertiser search, so each entry is a Top Ads query: { keyword, industry, region (ISO-2, default SA), objective, period (7|30|180, default 30), adFormat }. Without login TikTok shows the top 20 ads per query.

## `activeOnly` (type: `boolean`):

Keep only ads that are currently running (Google: shown within the last 7 days; LinkedIn: no end date yet or ended today).

## `lastNDays` (type: `integer`):

Ignore ads whose last-shown date is older than this many days. 0 = no limit.

## `maxAdsPerAdvertiser` (type: `integer`):

Cap per advertiser and platform. 0 = unlimited. Google needs one extra paced request per new ad to resolve its regions, so a first snapshot of many advertisers takes minutes; the Console form suggests 20.

## `emitChangesOnly` (type: `boolean`):

Output only change events (new\_ad, ad\_ended, creative\_refresh, new\_landing\_domain, new\_region, advertiser\_error) compared with the previous run under the same trackingId.

## `emitBaselineAsNew` (type: `boolean`):

On the first run for a trackingId, emit every ad as new\_ad instead of a silent baseline.

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

Name of the watch list. State is kept in the 'ad-intel-state' key-value store under this key. Required with 'Emit changes only'.

## `refreshThreshold` (type: `integer`):

New ads from one advertiser on one platform in a single run that count as a creative\_refresh event.

## `maxConcurrency` (type: `integer`):

Advertiser x platform sources checked in parallel (TikTok queries always run one at a time).

## `proxyConfiguration` (type: `object`):

Apify datacenter proxies work for Google and TikTok. LinkedIn blocks most datacenter ranges with HTTP 403: switch to residential proxies (RESIDENTIAL group) if LinkedIn sources fail.

## Actor input object example

```json
{
  "advertisers": [
    {
      "name": "HubSpot",
      "domain": "hubspot.com",
      "googleAdvertiserId": "AR10072600183532683265",
      "linkedinUrl": "https://www.linkedin.com/company/hubspot"
    },
    {
      "name": "monday.com",
      "domain": "monday.com",
      "googleAdvertiserId": "AR15972262369074085889",
      "linkedinUrl": "https://www.linkedin.com/company/mondaydotcom"
    }
  ],
  "platforms": [
    "google"
  ],
  "googleRegion": "anywhere",
  "linkedinCountries": [],
  "tiktokQueries": [
    {
      "keyword": "fitness app",
      "region": "SA",
      "period": 30
    },
    {
      "keyword": "fitness app",
      "region": "US",
      "period": 30
    }
  ],
  "activeOnly": true,
  "lastNDays": 90,
  "maxAdsPerAdvertiser": 20,
  "emitChangesOnly": false,
  "emitBaselineAsNew": false,
  "trackingId": "competitor-ads",
  "refreshThreshold": 5,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

Snapshot mode: one item per ad. Change mode: one item per change event.

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

Per advertiser x platform: ads found, requests, duration, proxy type, errors, charged events.

# 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 = {
    "advertisers": [
        {
            "name": "HubSpot",
            "domain": "hubspot.com",
            "googleAdvertiserId": "AR10072600183532683265",
            "linkedinUrl": "https://www.linkedin.com/company/hubspot"
        },
        {
            "name": "monday.com",
            "domain": "monday.com",
            "googleAdvertiserId": "AR15972262369074085889",
            "linkedinUrl": "https://www.linkedin.com/company/mondaydotcom"
        }
    ],
    "platforms": [
        "google"
    ],
    "googleRegion": "anywhere",
    "linkedinCountries": [],
    "tiktokQueries": [
        {
            "keyword": "fitness app",
            "region": "SA",
            "period": 30
        },
        {
            "keyword": "fitness app",
            "region": "US",
            "period": 30
        }
    ],
    "maxAdsPerAdvertiser": 20,
    "trackingId": "competitor-ads",
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("almoutasem_nabil/ad-library-intelligence").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 = {
    "advertisers": [
        {
            "name": "HubSpot",
            "domain": "hubspot.com",
            "googleAdvertiserId": "AR10072600183532683265",
            "linkedinUrl": "https://www.linkedin.com/company/hubspot",
        },
        {
            "name": "monday.com",
            "domain": "monday.com",
            "googleAdvertiserId": "AR15972262369074085889",
            "linkedinUrl": "https://www.linkedin.com/company/mondaydotcom",
        },
    ],
    "platforms": ["google"],
    "googleRegion": "anywhere",
    "linkedinCountries": [],
    "tiktokQueries": [
        {
            "keyword": "fitness app",
            "region": "SA",
            "period": 30,
        },
        {
            "keyword": "fitness app",
            "region": "US",
            "period": 30,
        },
    ],
    "maxAdsPerAdvertiser": 20,
    "trackingId": "competitor-ads",
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("almoutasem_nabil/ad-library-intelligence").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 '{
  "advertisers": [
    {
      "name": "HubSpot",
      "domain": "hubspot.com",
      "googleAdvertiserId": "AR10072600183532683265",
      "linkedinUrl": "https://www.linkedin.com/company/hubspot"
    },
    {
      "name": "monday.com",
      "domain": "monday.com",
      "googleAdvertiserId": "AR15972262369074085889",
      "linkedinUrl": "https://www.linkedin.com/company/mondaydotcom"
    }
  ],
  "platforms": [
    "google"
  ],
  "googleRegion": "anywhere",
  "linkedinCountries": [],
  "tiktokQueries": [
    {
      "keyword": "fitness app",
      "region": "SA",
      "period": 30
    },
    {
      "keyword": "fitness app",
      "region": "US",
      "period": 30
    }
  ],
  "maxAdsPerAdvertiser": 20,
  "trackingId": "competitor-ads",
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call almoutasem_nabil/ad-library-intelligence --silent --output-dataset

```

## MCP server setup

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

```

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/UBiNZu6FOMd7tN8Q3/builds/YnaaEqu5btsIzxlyZ/openapi.json
