# LinkedIn Ad Library Scraper & New Ads Monitor (`ivan-petrus-g/linkedin-ad-library-monitor`) Actor

Track competitors' LinkedIn ads from the public LinkedIn Ad Library. Search by company, keyword or payer and country, get ONLY NEW ads since the last run: ad copy, headline, CTA, format, images/video, landing page, payer, EU run dates, impressions and targeting.

- **URL**: https://apify.com/ivan-petrus-g/linkedin-ad-library-monitor.md
- **Developed by:** [Ivan Petrus](https://apify.com/ivan-petrus-g) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 ads

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 & New Ads Monitor

**LinkedIn Ad Library scraper and competitor ad monitor.** Enter competitor **company names**, **keywords** from the ad copy or **payers** ("Paid for by"), optionally filtered by **country** and **date range**. You get every matching ad from LinkedIn's public Ad Library: **ad copy, headline, CTA, format, image / video / document / carousel media, advertiser, landing page and payer**. For ads shown in the EU you also get **run dates, total impressions range, impressions by country and targeting** (language, location, audience, company, job, demographics...). With **"Only new ads"** on, every scheduled run returns **only ads that appeared since the last run**, so it works as a daily LinkedIn competitor-ads alert.

No LinkedIn account, no cookies: the actor reads the same public pages as [linkedin.com/ad-library](https://www.linkedin.com/ad-library/). It is built for reliability: polite 2–3 s pacing, retries with backoff, **proxy rotation on HTTP 429**, automatic switch to **residential proxy** when datacenter IPs get rate-limited, and a free summary row per search. **Failed searches are never charged.**

### Sample output

One ad row from a real Apify cloud run (Oct 2026, company search "HubSpot", countries DE + US), shortened:

```json
{
  "type": "ad",
  "searchQuery": "HubSpot",
  "format": "Single Image Ad",
  "advertiserName": "IXTENSA 🧡 HubSpot",
  "adCopy": "Viele HubSpot-Kunden kämpfen mit denselben Problemen – und lassen dadurch viel Potenzial liegen. …",
  "headline": "Live-Webinar am 14.10.2026 um 11 Uhr",
  "adId": "1558746953",
  "detailUrl": "https://www.linkedin.com/ad-library/detail/1558746953",
  "isNew": true
}
```

### What you get

| Data | Details |
|---|---|
| 🆕 New-ads diff | Remembers seen ads (named key-value store) and outputs only new ones on later runs. Each row has `isNew`. |
| ✍️ Creative | `adCopy`, `headline`, `cta`, `format` (Single Image, Video, Carousel, Document, Message, Article, Event, Text, Spotlight...), `imageUrl(s)`, `videoUrl` (+ thumbnail), `carouselCards` (title, image, link), `document` (title, page count, page images), `article` (newsletter, title) |
| 🏢 Advertiser | `advertiserName`, `advertiserCompanyId`, `advertiserUrl`, logo, `paidBy` |
| 🔗 Landing page | `landingPageUrl` with UTM parameters (often reveals campaign names and tools) |
| 🇪🇺 EU transparency data (with details) | `startDate`, `endDate`, `daysRunning`, `impressions` ("5k-10k") + numeric min/max, `impressionsByCountry` (% share), `targeting` (language, location values...), `targetingParameters` (which of Audience / Company / Job / Education / Demographic / Interests were targeted or excluded) |
| 📋 Summary row (free) | Per search: ads fetched, **new ads since last run**, formats, top advertisers, and a link to the same search on LinkedIn |

### How it works

1. **Search** (cheap): each company / keyword / payer is searched in the Ad Library, newest ads first, 24 ads per page, following LinkedIn's pagination until your limit.
2. **Details** (optional, `includeDetails`): one extra request per ad opens its detail page for the full copy, CTA, landing page, payer and EU data. Without details, ad copy in search results is often shortened by LinkedIn (`adCopyTruncated: true`).
3. **Diff**: seen ad IDs are stored per search + filters, so the next run outputs only new ads.

### Use cases

- **Daily LinkedIn competitor alerts** for B2B marketers, ABM and demand-gen teams: schedule it and send new ads to Slack, email, Google Sheets or a webhook.
- **Creative and messaging research:** which offers, formats, CTAs and landing pages competitors test, and how long ads run.
- **Agencies:** monitor many client competitors in one run; `paidBy` shows which agency or entity pays.
- **Targeting intelligence (EU ads):** see which countries get impressions and whether competitors target by job, company, audience or interests.
- **Keyword research:** who advertises on "crm", "sales automation", "cybersecurity"... in a given country.

### Input examples

**Daily monitor of competitors (new ads only):**

```json
{ "companies": ["HubSpot", "Salesforce"], "onlyNewAds": true, "maxAdsPerSearch": 50 }
```

**Full details for EU ads in Germany and France, last 30 days:**

```json
{ "companies": ["SAP"], "countries": ["DE", "FR"], "dateRange": "last-30-days", "includeDetails": true, "maxAdsPerSearch": 30 }
```

**Keyword research, exact advertiser only, without member (thought leader) ads:**

```json
{ "keywords": ["crm"], "countries": ["US"], "maxAdsPerSearch": 100, "onlyNewAds": false }
```

```json
{ "companies": ["HubSpot"], "exactCompanyMatch": true, "skipMemberAds": true }
```

**Start monitoring silently (record current ads, output nothing on the first run):**

```json
{ "companies": ["Notion"], "firstRunBehavior": "baselineOnly", "stateStoreName": "notion-linkedin" }
```

### Output example (with details)

```json
{
  "type": "ad",
  "searchType": "company",
  "searchQuery": "SAP Fioneer",
  "adId": "1633089726",
  "format": "Video Ad",
  "advertiserType": "company",
  "advertiserName": "SAP Fioneer",
  "advertiserCompanyId": "74481653",
  "adCopy": "Insurance broker files can contain hundreds of settlement items. See how FS-CD helps clean items move forward and keeps exceptions visible.",
  "headline": "Bring structure to broker settlements",
  "cta": "Learn more",
  "landingPageUrl": "https://www.sapfioneer.com/blog/why-broker-settlements-belong-inside-receivables-control/?utm_campaign=FS-CD+%257C+Insurance+%257C+2026&utm_source=linkedin&utm_medium=paid",
  "videoUrl": "https://dms.licdn.com/playlist/vid/v2/D5610AQGMhMNDO8Y5Yw/progressive-servable-video/...",
  "paidBy": "SAP Fioneer GmbH",
  "startDate": "2026-10-06",
  "endDate": "2026-10-08",
  "impressions": "5k-10k",
  "impressionsByCountry": null,
  "targeting": { "Language": { "values": ["English"] }, "Location": { "values": ["United States", "Canada", "Europe"] } },
  "targetingParameters": [ { "parameter": "Company", "targeted": true, "excluded": true }, { "parameter": "Job", "targeted": true, "excluded": false } ],
  "detailUrl": "https://www.linkedin.com/ad-library/detail/1633089726",
  "isNew": true
}
```

`impressionsByCountry` fills in once LinkedIn updates it (up to 48 h), e.g. `[{ "country": "Germany", "impressionShare": 69.0 }, { "country": "Switzerland", "impressionShare": 20.0 }, { "country": "France", "impressionShare": 1.0, "lessThan": true }]`.

**Summary row (free):** `{ "type": "summary", "searchQuery": "HubSpot", "adsFetched": 40, "newAdsSinceLastRun": 10, "formats": { "Single Image Ad": 35, "Video Ad": 5 }, "adLibraryUrl": "https://www.linkedin.com/ad-library/search?accountOwner=HubSpot" }`

Failed searches appear as `{ "type": "error", "status": "failed", "error": "...", "hint": "..." }` and are **not charged**.

### Pricing (pay per event)

- **$0.005 per search** (one company, keyword or payer, all its result pages, summary row included)
- **$1.50 per 1,000 ads** ($0.0015 per ad row). With "Only new ads" on, you pay only for new ads.
- **+$1.50 per 1,000 ads with details** ($0.0015 extra per ad, only when the detail page was fetched)
- **$0.005 per run** start fee. No proxy or compute costs on top. Summary and error rows are free.
- Example: 10 competitors monitored daily, ~5 new ads each, with details: **about $0.21 per day**.

Set **Max ads per search** or a maximum cost per run to cap spending. If the run stops at your limit, undelivered new ads are **not** marked as seen, so they arrive next time.

### Privacy: no personal profile data

Some LinkedIn ads are posted from an individual member's profile ("thought leader" ads) or sent as messages by a person. For those, the actor **never outputs the member's name, headline, photo or profile URL**: `advertiserType` is `member`, and `advertiserName` shows the company behind the ad when LinkedIn shows one ("Promoted by" / "Paid for by"). If a member ad was paid for by a private person, `paidBy` is removed (`paidByRedacted: true`). Turn on `skipMemberAds` to drop member ads completely. Ad copy is returned as published by the advertiser.

### Limits you should know

- **EU data only for EU ads.** LinkedIn publishes run dates, impressions and targeting only for ads shown in the EU (Digital Services Act). Other ads have creative, advertiser and payer only. Impressions by country can take up to 48 hours to appear.
- **About one year of history.** LinkedIn keeps ads in the library for roughly a year after they last ran.
- **Company search is a name search.** LinkedIn matches the text you enter, so "HubSpot" also returns partner pages like "HubSpot Platinum Partner". Use `exactCompanyMatch` for one exact advertiser name.
- **Ad copy without details** may be shortened by LinkedIn; turn on `includeDetails` for the full text.
- **Rate limits.** LinkedIn throttles fast clients (HTTP 429). The actor paces requests, rotates proxy IPs and falls back to residential proxy; very large runs simply take longer.
- **Markup changes.** This reads public web pages, not an official API. If LinkedIn changes its layout, runs fail loudly (error rows, not silent empty data) until the actor is updated.

### Daily alerts to Slack or email

1. Fill in the input and click **Save as a new task** (one task per client or competitor set is a good pattern).
2. In **Schedules**, create a schedule (for example every day at 08:00 in your time zone) and add the task.
3. In the task's **Integrations** tab, add the **Slack** or **Gmail** integration to get a message when a run finishes,
   or a **webhook** on "Run succeeded" that passes the run to Zapier, Make, n8n or your own endpoint. Those tools can read the rows from
   `https://api.apify.com/v2/datasets/{defaultDatasetId}/items` and format them however you like.

Because monitor mode outputs **only the ads that appeared since the previous run**, every scheduled run's dataset *is* your alert list. An empty dataset (apart from free summary rows) means nothing changed.
Turn on Apify's run-failure notifications too, so you hear about a failed run instead of silence.

### Related actors

Part of a small **competitor-intelligence suite** by the same developer. Same conventions everywhere: pay per event, failed items are never charged, and the monitors return only what changed since the last run.

- [Google Trends Scraper & API](https://apify.com/ivan-petrus-g/google-trends-api): interest over time, by region/city, top queries and Trending now for any country.
- [Google Ads Transparency Scraper & New Ads Monitor](https://apify.com/ivan-petrus-g/google-ads-transparency-monitor): competitors' Google Search, Display and YouTube ads, with only-new-ads alerts.
- [Bing Ads Library Scraper - Microsoft Ads Monitor (EU)](https://apify.com/ivan-petrus-g/microsoft-ads-library-monitor): Bing ads from Microsoft's official Ad Library (EU/EEA), with impressions by country.
- [ATS Jobs Scraper & Hiring Monitor](https://apify.com/ivan-petrus-g/company-hiring-monitor): new and closed jobs from Greenhouse, Lever, Ashby, Workday and 6 more job boards, by company domain.
- [App Store & Google Play Scraper](https://apify.com/ivan-petrus-g/app-store-monitor): ratings, installs, versions, chart and keyword ranks of iOS and Android apps, with change rows.

### FAQ

**What is the LinkedIn Ad Library?** LinkedIn's public archive of ads that ran on LinkedIn in the last year, with the advertiser, payer and, for EU ads, dates, reach and targeting. This actor returns it as JSON, CSV or Excel.

**Do I need a LinkedIn account or cookies?** No. Only public, logged-out pages are used.

**Does it show spend or clicks?** No. LinkedIn publishes impression ranges (EU ads), not spend or clicks.

**Can I monitor by LinkedIn company URL?** Yes, `linkedin.com/company/<name>` URLs are accepted and converted to a name search.

**Can I get alerts?** Yes. Schedule the actor and connect the dataset to Slack, email, Google Sheets, Zapier, Make or n8n. With "Only new ads", each run's dataset is your list of new ads.

**Is it legal?** It collects publicly available ad-transparency data without logging in and without personal profile data. You are responsible for complying with LinkedIn's terms and applicable law (e.g. GDPR) in how you use the data.

*Keywords: linkedin ad library scraper, linkedin ads scraper, linkedin ads library api, linkedin competitor ads, linkedin ad spy, b2b ads monitor, new ads alert, linkedin ad intelligence, competitor ad tracking, abm competitor research.*

# Actor input Schema

## `companies` (type: `array`):

Company / advertiser names as shown on LinkedIn, e.g. 'HubSpot'. LinkedIn company URLs (linkedin.com/company/hubspot) are accepted too. LinkedIn matches names loosely; turn on 'Exact company match' to drop partners and lookalikes.

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

Find ads that mention these words or phrases, e.g. 'crm', 'sales automation'. Each keyword is one search.

## `payers` (type: `array`):

Search by the 'Paid for by' entity, e.g. 'HubSpot, Inc.'. Each payer is one search.

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

Only ads shown in these countries (2-letter codes: US, GB, DE, FR...). Empty = all countries.

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

When the ads ran. 'custom' uses startDate/endDate. LinkedIn keeps ads for about one year.

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

YYYY-MM-DD, used when Date range = custom.

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

YYYY-MM-DD, defaults to today.

## `includeDetails` (type: `boolean`):

Opens each ad's detail page (1 extra request per ad, charged as 'ad details'): full ad copy, CTA button, landing page URL, 'Paid for by', all carousel cards / document pages and, for ads shown in the EU, run dates, total impressions range, impressions by country and targeting (language, location, audience, company, job...).

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

Newest ads first. 0 = all ads (can be thousands for big advertisers and keywords).

## `exactCompanyMatch` (type: `boolean`):

Keep only ads whose advertiser name equals the company you entered (LinkedIn's search also returns partners and similarly named pages).

## `skipMemberAds` (type: `boolean`):

Drop 'thought leader' ads that are posted from a person's profile. They are never output with personal data anyway: the member's name, photo and profile are always removed.

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

Remember which ads were already seen (in a named key-value store) and output only new ones. Perfect for daily schedules.

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

outputAll = the first run returns all current ads (up to the limit). baselineOnly = the first run only records current ads; later runs return what's new (cheaper).

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

Named key-value store that keeps seen ad IDs. Use different names for independent monitors.

## `includeSummary` (type: `boolean`):

Adds one free summary row per search: ads fetched, new ads, formats, top advertisers and a link to the same search on LinkedIn.

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

Searches processed in parallel, each with its own proxy session.

## `requestDelaySecs` (type: `number`):

Pause between two requests of one worker (polite pacing; LinkedIn rate-limits fast clients).

## `maxRetries` (type: `integer`):

Retries with backoff and a new proxy IP on 429/999/5xx/login walls.

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

Apify Proxy is required for reliable runs. Datacenter proxy is used first.

## `residentialFallback` (type: `boolean`):

If datacenter IPs get rate-limited (HTTP 429) repeatedly, switch that worker to Apify residential proxy.

## Actor input object example

```json
{
  "companies": [
    "HubSpot"
  ],
  "dateRange": "any",
  "includeDetails": false,
  "maxAdsPerSearch": 5,
  "exactCompanyMatch": false,
  "skipMemberAds": false,
  "onlyNewAds": true,
  "firstRunBehavior": "outputAll",
  "stateStoreName": "linkedin-ads-monitor-state",
  "includeSummary": true,
  "maxConcurrency": 2,
  "requestDelaySecs": 2.5,
  "maxRetries": 6,
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "residentialFallback": true
}
```

# Actor output Schema

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

No description

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

No description

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

No description

# 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 = {
    "companies": [
        "HubSpot"
    ],
    "maxAdsPerSearch": 5,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("ivan-petrus-g/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 = {
    "companies": ["HubSpot"],
    "maxAdsPerSearch": 5,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("ivan-petrus-g/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 '{
  "companies": [
    "HubSpot"
  ],
  "maxAdsPerSearch": 5,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call ivan-petrus-g/linkedin-ad-library-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,ivan-petrus-g/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/hGQMDSJENZhfxS3E8/builds/Hy9xZpErtqVGD6s15/openapi.json
