# LinkedIn Ad Library Scraper & Competitor Ad Monitor (`yushi3/linkedin-ad-library-monitor`) Actor

Scrape LinkedIn's Ad Library by company or keyword: ad copy, headline, CTA, landing page, images and videos, run dates, impressions by country and targeting. Monitor mode returns only new ads, with webhook alerts. Pay per ad.

- **URL**: https://apify.com/yushi3/linkedin-ad-library-monitor.md
- **Developed by:** [Yushi Homma](https://apify.com/yushi3) (community)
- **Categories:** Social media, Lead generation
- **Stats:** 2 total users, 1 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 ad with details

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## LinkedIn Ad Library Scraper & Competitor Ad Monitor

See every ad a company runs on LinkedIn, from LinkedIn's public [Ad Library](https://www.linkedin.com/ad-library), and get told when they launch a new one. Search by company name or by keyword. Each ad comes back as a flat row: advertiser, format, **full ad copy, headline, call-to-action, landing page, image and video links**. For ads shown in the EU it also has **run dates, impressions by country and the targeting** the advertiser chose.

- **Everything on the ad's page**: the full copy (search results cut it off), CTA button, landing page, the MP4 of video ads, who paid for the ad, and, for EU-shown ads, first and last day shown, total impressions, impressions per country, and language, location, company, job and audience targeting.
- **Search by company or keyword**: `hubspot`, `Salesforce`, or ads that mention `AI agents`, optionally limited to countries and a date range. Ad Library search links work too, with all their filters.
- **Competitor monitoring built in**: every row says whether the ad is `newSinceLastRun`. Turn on **Only new ads** to get (and pay for) only new ads, and add a webhook to be alerted about each one.
- **No login, no cookies, no LinkedIn account at risk**: the Ad Library is public. Requests use a Firefox browser fingerprint, and switch to a fresh IP automatically when LinkedIn's Cloudflare blocks one.
- **API and AI agent tool**: ask "what is this company advertising on LinkedIn?" in one API call, or let an AI agent call it as a tool through the Apify MCP server.

### Use cases

- Watch competitors' LinkedIn ads and hear about each new campaign the day it appears.
- Collect B2B ad copy, headlines and CTAs to benchmark messaging, offers and creative formats.
- See where a competitor spends: impressions by country and the job, company and location targeting of their EU ads.
- Find every advertiser talking about a topic (`keywords`), e.g. to build a list of companies investing in a category.
- Feed ads into an LLM for analysis, or give an AI agent a "what is X advertising on LinkedIn?" tool.

### How to use it

1. Add **Advertisers**: company names as you'd type them into the Ad Library (`hubspot`), or Ad Library search links (`https://www.linkedin.com/ad-library/search?accountOwner=hubspot`).
2. Or add **Keywords** to find ads that mention a word or phrase, from any advertiser.
3. Optionally limit to **Countries** (`US`, `DE`…) and a period (**Shown during**).
4. Choose **Max ads per advertiser or keyword** (default 50).
5. For monitoring, turn on **Only new ads**, optionally set an **Alert webhook URL**, and run the Actor on a [schedule](https://docs.apify.com/platform/schedules), for example daily. The first run returns everything and becomes the baseline; later runs return only new ads.

### Tutorial: get a Slack message when a competitor launches a LinkedIn ad

1. In [Zapier](https://zapier.com), [Make](https://www.make.com) or [n8n](https://n8n.io), create a scenario that starts with a **webhook trigger** ("Catch Hook" in Zapier, "Custom webhook" in Make, "Webhook" node in n8n) and copy its URL.
2. Add a **Slack → Send message** step. Map `item.advertiser`, `item.headline`, `item.adText` and `item.adUrl` into the message.
3. In this Actor, enter your competitors under **Advertisers**, turn on **Only new ads**, and paste the webhook URL into **Alert webhook URL**.
4. Run it once. That run records the ads already live as the baseline and sends no alerts.
5. Open **Schedules** in Apify Console and run the Actor daily with the same input. From then on, each new ad becomes one Slack message, and you pay only for new ads.

The same webhook works for email, Microsoft Teams, Google Sheets or a CRM. Add a different action after the trigger.

### Input

```json
{
  "advertisers": ["hubspot", "https://www.linkedin.com/ad-library/search?accountOwner=salesforce&countries=DE"],
  "keywords": ["AI agents"],
  "countries": ["US", "GB"],
  "dateOption": "last-30-days",
  "maxAdsPerSearch": 50,
  "scrapeDetails": true,
  "onlyNewAds": true,
  "alertWebhookUrl": "https://hooks.example.com/linkedin-ads"
}
```

| Field | Required | Description |
| --- | --- | --- |
| `advertisers` | one of these two | Company names or Ad Library search URLs. |
| `keywords` | one of these two | Words or phrases in the ad copy, or Ad Library search URLs. |
| `countries` | no | Two-letter country codes. Only ads shown there are returned. A search URL that sets its own countries keeps them. |
| `dateOption` | no | `all` (default), `last-30-days`, `current-month`, `current-year` or `last-year`. |
| `maxAdsPerSearch` | no | 1 to 5,000 ads per advertiser or keyword (default 50). |
| `scrapeDetails` | no | Open each ad's page for the full copy, CTA, landing page, video, payer, run dates, impressions and targeting (default `true`). |
| `onlyNewAds` | no | Return only ads not seen in earlier runs for the same advertiser or keyword (default `false`). |
| `alertWebhookUrl` | no | Receives a POST for every ad that appeared since the previous run. |
| `proxyConfiguration` | no | Leave empty: LinkedIn is fetched directly, falling back to Apify datacenter and then residential proxies only when it blocks a request. |

### Output

One dataset item per ad:

```json
{
  "adId": "1605968824",
  "adUrl": "https://www.linkedin.com/ad-library/detail/1605968824",
  "advertiser": "HubSpot",
  "advertiserUrl": "https://www.linkedin.com/company/68529",
  "advertiserLogoUrl": "https://media.licdn.com/dms/image/v2/…/hubspot_logo",
  "paidBy": "HubSpot, Inc.",
  "format": "Video Ad",
  "creativeType": "SPONSORED_VIDEO",
  "adText": "Dein CRM ist nur so gut wie seine Daten. Das HubSpot Smart CRM aktualisiert sich selbst…",
  "headline": "Ein CRM – clever und selbst aktualisierend.",
  "cta": "Mehr erfahren",
  "landingUrl": "https://www.hubspot.de/products/crm/ai-crm-lp?utm_source=linkedin…",
  "imageUrl": "https://media.licdn.com/dms/image/v2/…/videocover-high/…",
  "imageUrls": ["https://media.licdn.com/dms/image/v2/…/videocover-high/…"],
  "videoUrl": "https://dms.licdn.com/playlist/vid/v2/…/progressive-servable-video/…",
  "runFrom": "2026-10-22",
  "runTo": "2026-10-25",
  "totalImpressions": "50k-100k",
  "impressionsByCountry": [{ "country": "Germany", "impressions": "100%" }, { "country": "Austria", "impressions": "less than 1%" }],
  "targeting": [
    { "parameter": "Language", "value": "Targeting includes Deutsch" },
    { "parameter": "Location", "value": "Targeting includes Deutschland" },
    { "parameter": "Company", "value": "targeted and excluded" },
    { "parameter": "Job", "value": "targeted and excluded" },
    { "parameter": "Education", "value": "not used" }
  ],
  "detailScraped": true,
  "source": "advertiser:hubspot",
  "newSinceLastRun": true,
  "scrapedAt": "2026-10-25T21:47:59.693Z"
}
```

- `runFrom`, `runTo`, `totalImpressions`, `impressionsByCountry` and `targeting` come from LinkedIn's EU transparency data, so they are filled only for ads shown in the EU. They are `null` or empty for other ads.
- `targeting` lists language and location segments as LinkedIn words them, then says for audience, demographic, company, education, job and interest criteria whether each was `targeted`, `excluded`, `targeted and excluded` or `not used`.
- `landingUrl` is `null` when LinkedIn doesn't show one (e.g. lead-gen form ads). Links inside the copy stay in `adText`.
- With **Open each ad's page** off, `adText` is the preview from search results (cut off after a few lines), and `cta`, `landingUrl`, `videoUrl`, `paidBy` and the EU fields are empty.
- `advertiser` is the company, or the person for thought-leader ads promoted by a company.

The webhook body is `{ "actor": "linkedin-ad-library-monitor", "item": { ...same fields... }, "detectedAt": "<ISO time>" }`.

### API and AI agents

Run it and get the results in one call:

```bash
curl -X POST "https://api.apify.com/v2/acts/yushi3~linkedin-ad-library-monitor/run-sync-get-dataset-items" \
  -H "Authorization: Bearer <APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"advertisers": ["hubspot"]}'
```

It returns the dataset items as a JSON array. AI agents can call the Actor as a tool through the [Apify MCP server](https://mcp.apify.com).

### Pricing

| Event | Price |
| --- | --- |
| Ad with details (ad page read: full copy, CTA, landing page, video, run dates, impressions, targeting) | $0.002 |
| Ad from search results only (with **Open each ad's page** off) | $0.001 |
| New ad alert delivered to your webhook | $0.002 |

Proxy traffic is included. With **Only new ads** on, ads you already received are neither returned nor charged. When LinkedIn keeps blocking an ad's page even after fresh IPs, the ad is still returned from search results and billed as a search result.

### Limitations

- Only what LinkedIn's Ad Library shows publicly. LinkedIn shows run dates, impressions and targeting only for ads shown in the EU, and never shows spend.
- LinkedIn lists 24 ads per results page. Company search matches LinkedIn's own advertiser name search, so `hubspot` also finds "HubSpot for Developers".
- Video links (`videoUrl`) and image links are signed by LinkedIn and may stop working after some time. Download them if you need to keep them.

### When an advertiser or keyword fails

Every advertiser and keyword is processed even if one fails. Failures are listed in the run's key-value store under `FAILED_TARGETS` with the reason. The run fails outright only when LinkedIn changed its pages in a way the Actor can no longer read (it is re-tested against the live site every 12 hours) or when every advertiser and keyword failed.

### Questions and issues

Open an issue on the Actor's **Issues** tab.

# Actor input Schema

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

Company names as you'd type them in the Ad Library (hubspot, Salesforce), or Ad Library search URLs (https://www.linkedin.com/ad-library/search?accountOwner=hubspot), which keep all their filters.

## `keywords` (type: `array`):

Words or phrases to find in ad copy, across all advertisers (e.g. CRM, "AI agents"), or Ad Library search URLs.

## `countries` (type: `array`):

Optional. Two-letter country codes (US, DE, GB…). Only ads shown in these countries are returned. Applies to every advertiser and keyword unless its search URL already sets countries.

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

Only ads shown during this period.

## `maxAdsPerSearch` (type: `integer`):

Stop after this many ads for each advertiser or keyword (24 per results page).

## `scrapeDetails` (type: `boolean`):

Read each ad's own page for the full copy, CTA, landing page, video file, payer and, for ads shown in the EU, run dates, impressions by country and targeting. Turn off for a faster, cheaper list of ads from search results only.

## `onlyNewAds` (type: `boolean`):

Return (and charge for) only ads not seen in earlier runs for the same advertiser or keyword. The first run returns everything and becomes the baseline. Turn on for scheduled monitoring.

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

Optional. Receives a POST for each ad that appeared since the previous run (Slack, Zapier, Make, n8n).

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

Optional. By default LinkedIn is fetched directly and falls back to Apify datacenter, then residential proxies, with fresh IPs, only when LinkedIn blocks a request. Set a proxy here to use it for every request instead.

## Actor input object example

```json
{
  "advertisers": [
    "https://www.linkedin.com/ad-library/search?accountOwner=hubspot"
  ],
  "dateOption": "all",
  "maxAdsPerSearch": 8,
  "scrapeDetails": true,
  "onlyNewAds": false
}
```

# Actor output Schema

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

One item per ad: advertiser, copy, headline, CTA, media, run dates, impressions, targeting and whether it is new since the previous 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 = {
    "advertisers": [
        "https://www.linkedin.com/ad-library/search?accountOwner=hubspot"
    ],
    "maxAdsPerSearch": 8
};

// Run the Actor and wait for it to finish
const run = await client.actor("yushi3/linkedin-ad-library-monitor").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": ["https://www.linkedin.com/ad-library/search?accountOwner=hubspot"],
    "maxAdsPerSearch": 8,
}

# Run the Actor and wait for it to finish
run = client.actor("yushi3/linkedin-ad-library-monitor").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": [
    "https://www.linkedin.com/ad-library/search?accountOwner=hubspot"
  ],
  "maxAdsPerSearch": 8
}' |
apify call yushi3/linkedin-ad-library-monitor --silent --output-dataset

```

## MCP server setup

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

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/0wB06h9poQ0p0t4hC/builds/9nMa2K05oLSjmLEyw/openapi.json
