# LinkedIn Ad Library Scraper (LinkedIn Ads Library) (`pulsedata/linkedin-ad-library-scraper`) Actor

Extract every ad a company runs on LinkedIn from the public Ad Library: ad copy, creative, headline, destination URL, run dates, estimated impressions per country and the advertiser's targeting. Search by advertiser or keyword.

- **URL**: https://apify.com/pulsedata/linkedin-ad-library-scraper.md
- **Developed by:** [PulseData](https://apify.com/pulsedata) (community)
- **Categories:** Marketing, Lead generation, Agents
- **Features:** Limited permissions
- **Stats:** 16 total users, 5 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 ad scrapeds

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 (LinkedIn Ads Library)

This actor extracts the ads a company runs on LinkedIn from the public [LinkedIn Ad Library](https://www.linkedin.com/ad-library/home): ad copy, creative, headline, destination URL, run dates, estimated impressions per country and the targeting the advertiser selected. You search by advertiser, payer, keyword, Ad Library search URL or single ad ID, and get one JSON item per ad. It costs $2 per 1,000 ads, plus $3 per 1,000 ads for the detail page when `includeDetails` is on, plus $0.01 per run. No LinkedIn account or cookies are needed.

### Features

- **Scrape all LinkedIn ads of a company** by advertiser page name.
- **Find every LinkedIn ad paid for by a company group** with `matchBy: payer`, which catches subsidiaries whose ads the parent pays for.
- **Search LinkedIn ads by keyword** in the ad text.
- **See LinkedIn ad targeting and impressions per country** for ads under the EU transparency rules.
- **Filter LinkedIn ads by country and date range**: last 30 days, this month, this year, last year or a custom range.
- **Track new LinkedIn ads of a competitor** with a scheduled run that returns only ads not delivered before.
- **Export LinkedIn Ad Library data to CSV, Excel or JSON** through the Apify dataset.

### Use cases

- **B2B competitor and creative research**: every creative a competitor runs, how long each ran, which countries got the impressions, and the full ad text for swipe files or LLM analysis.
- **Audience research**: whether the advertiser targeted by company, job, education, demographics or interests, plus language and locations, including exclusions.
- **Agency pitches**: a prospect's current LinkedIn ads, run dates and reach.
- **Lead generation**: check which companies advertise on LinkedIn at all. Pass a list of names and set `maxAdsPerQuery` to 1.
- **Brand monitoring**: catch ads that mention your brand with `matchBy: keyword`.

### Input

```json
{
  "queries": ["Salesforce"],
  "matchBy": "payer",
  "countryCode": "de",
  "dateOption": "last-30-days",
  "maxAdsPerQuery": 200,
  "includeDetails": true
}
```

| Field | Type | Default | Meaning |
|---|---|---|---|
| `queries` | array | (required) | Advertiser names, keywords, Ad Library search URLs, ad URLs or bare ad IDs, one per line |
| `matchBy` | select | `advertiser` | `advertiser` (the LinkedIn page running the ad), `payer` (the entity that paid) or `keyword` (the ad text) |
| `countryCode` | string | empty | ISO code such as `us`, `de`, `gb`; a country name such as `Germany` is converted. Empty means all countries |
| `dateOption` | select | any time | `last-30-days`, `current-month`, `current-year`, `last-year` or `custom-date-range` |
| `startDate`, `endDate` | date | empty | `YYYY-MM-DD`, used with the custom range |
| `maxAdsPerQuery` | integer | 100 | `0` means no limit |
| `includeDetails` | boolean | true | Opens each ad's detail page for impressions, targeting, full copy and destination URL |
| `proxyConfiguration` | object | Apify Unblocker | Leave as is unless you know your proxies get through |
| `onlyNewAds` | boolean | false | Monitor mode: output and charge only ads not delivered on an earlier run |
| `monitorStoreName` | string | empty | Key-value store that remembers delivered ads. Empty means a name derived from the queries and filters |

An Ad Library search URL you built in the browser is used with its filters as given.

### Track new LinkedIn ads of a competitor

Turn on `onlyNewAds` to get only the ads an advertiser launched since your last run:

```json
{ "queries": ["Salesforce"], "matchBy": "payer", "maxAdsPerQuery": 0, "onlyNewAds": true }
```

The actor stores the ID of every delivered ad in a key-value store in your Apify account. On later runs those ads are skipped before their detail page is fetched, so they are neither output nor charged. The first run returns every ad up to `maxAdsPerQuery` and fills the store. Save the input as a task, add an [Apify schedule](https://docs.apify.com/platform/schedules) and an integration (email, Slack, Google Sheets, Make, Zapier or a webhook).

- `maxAdsPerQuery` sets how far down the list a run looks, and ads seen before count towards it. Use `0` to watch every ad.
- Without `monitorStoreName` each different input keeps its own history; the run log names the store.
- An ad cut off by the run timeout or the maximum cost per run is not remembered and comes on the next run.
- Schedule one run per store; two parallel runs can both deliver the same new ad.

### Output

One item per ad:

```json
{
  "query": "Zalando SE",
  "adId": "1552638623",
  "url": "https://www.linkedin.com/ad-library/detail/1552638623",
  "advertiserName": "ZEOS",
  "advertiserProfileUrl": "https://www.linkedin.com/company/112941442",
  "advertiserCompanyId": "112941442",
  "paidBy": "Zalando SE",
  "adType": "Single Image Ad",
  "commentary": "What future is business creating today? Join our live session on loyalty in the agentic era.",
  "headline": "90 minutes, live online",
  "destinationUrl": "https://zeos.eu/loyalty-in-the-agentic-era",
  "imageUrls": ["https://media.licdn.com/dms/image/..."],
  "availabilityText": "Ran from Jun 19, 2026 to Jul 6, 2026",
  "firstShown": "2026-06-19",
  "lastShown": "2026-07-06",
  "totalImpressions": "5k-10k",
  "impressionsLow": 5000,
  "impressionsHigh": 10000,
  "impressionsByCountry": [
    { "country": "United Kingdom", "sharePercent": 18 },
    { "country": "Germany", "sharePercent": 15 }
  ],
  "topCountry": "United Kingdom",
  "targeting": {
    "language": "Targeting includes English",
    "location": "Targeting includes European Union | Targeting excludes Kazakhstan, India, Russia",
    "parameters": [
      { "parameter": "Company", "targeted": true, "excluded": false },
      { "parameter": "Job", "targeted": true, "excluded": false }
    ]
  },
  "hasTransparencyData": true,
  "scrapedAt": "2026-09-25T08:00:00.000Z"
}
```

Items also include `advertiserLogoUrl`, `creativeType` and a `detailed` flag. The keys inside `targeting` follow the headings LinkedIn shows on the ad, so they can vary. Without `includeDetails` you get the card data only: advertiser, ad type, copy as shown on the card, headline and images.

### What LinkedIn publishes

LinkedIn shows impressions, run dates and targeting only for ads under the EU transparency rules; a US-only campaign usually has an "About the ad" box and nothing else. `hasTransparencyData` tells you which case you have. For impressions and targeting, set a European `countryCode`. Spend and clicks are not published.

### Pricing

| Event | Price |
|---|---|
| `ad-scraped`, per ad returned | $0.002 |
| `ad-detail-scraped`, per ad detail page loaded (only with `includeDetails` on) | $0.003 |
| `apify-actor-start`, per run | $0.01 (charged again per GB of memory above 1 GB) |

The detail event is charged for every ad whose detail page loads, whether or not LinkedIn publishes impressions and targeting for it. Detail pages that time out or are blocked are not charged.

Worked example: 200 ads of one advertiser with `includeDetails` on cost 200 × $0.002 + 200 × $0.003 + $0.01 = $1.01. The same run without details costs $0.41.

In monitor mode (`onlyNewAds`) only new ads are charged. A daily run with `includeDetails` on that finds 10 new ads costs 10 × $0.002 + 10 × $0.003 + $0.01 = $0.06, and a run with nothing new costs only the $0.01 start.

Until 2026-10-12 the detail event was charged only for ads that carry impressions, run dates or targeting; the prices per event did not change.

### FAQ

**Why does an advertiser search return unrelated companies?**
LinkedIn matches advertiser names loosely. Use `matchBy: payer` for the legal entity, or paste an Ad Library search URL.

**Can I get all ads of a company, not just 100?**
Set `maxAdsPerQuery` to `0`. Large advertisers have thousands of ads and the run charges per ad, so set a maximum charge on the run.

**Which proxies does it need?**
LinkedIn answers datacenter and residential proxies with a challenge page, so the actor uses the Apify Unblocker by default and falls back to it if the configured proxy is not available on your account.

**How fast is it?**
Detail pages are fetched four at a time. Expect roughly 2 to 4 minutes per 100 ads with details and a few seconds per 100 without.

**How current is the data?**
Each run reads the Ad Library live at that moment.

### Use it from Claude, ChatGPT or Cursor (Apify MCP server)

Any AI assistant that speaks the Model Context Protocol can call this actor. Add the Apify MCP server with this URL:

```text
https://mcp.apify.com?tools=pulsedata/linkedin-ad-library-scraper
```

Then ask in plain language, for example "show the LinkedIn ads HubSpot ran in Germany in the last 30 days", and the assistant fills the input, runs the actor and reads the ads back. Billing is the same as a normal run.

### Paying from an AI agent without an Apify account (x402 and Skyfire)

An agent can buy a prepaid, spend-capped Apify token at [Apify AGI](https://agi.apify.com), paid in USDC through [x402](https://docs.apify.com/platform/integrations/x402) or from a [Skyfire](https://docs.apify.com/platform/integrations/skyfire) wallet, and use it for both the API and the MCP server. This actor runs with limited permissions, so it works with these tokens. The smallest token is $1, tokens expire 14 days after purchase, and Apify marks agentic payments as experimental.

### Example pages

- [See every ad a competitor runs on LinkedIn](https://apify.com/pulsedata/linkedin-ad-library-scraper/examples/competitor-linkedin-ads)
- [All LinkedIn ads paid for by one company group](https://apify.com/pulsedata/linkedin-ad-library-scraper/examples/linkedin-ads-by-payer)
- [Who does this company target on LinkedIn?](https://apify.com/pulsedata/linkedin-ad-library-scraper/examples/linkedin-ad-targeting-and-impressions)
- [Find LinkedIn ads that mention a keyword](https://apify.com/pulsedata/linkedin-ad-library-scraper/examples/linkedin-ads-by-keyword)
- [LinkedIn ads shown in Germany](https://apify.com/pulsedata/linkedin-ad-library-scraper/examples/linkedin-ads-germany)
- [See the LinkedIn ads a SaaS company runs](https://apify.com/pulsedata/linkedin-ad-library-scraper/examples/linkedin-ads-of-a-saas-company)
- [LinkedIn ads shown in the United States](https://apify.com/pulsedata/linkedin-ad-library-scraper/examples/linkedin-ads-united-states)
- [Find LinkedIn ads for hiring and recruiting](https://apify.com/pulsedata/linkedin-ad-library-scraper/examples/linkedin-recruiting-ads)

### Related actors

- [Google Ads Transparency Center Scraper](https://apify.com/pulsedata/google-ads-transparency-scraper): the same competitor view for Google Search, YouTube, Shopping, Maps and Play ads.
- [TikTok Ad Library Scraper](https://apify.com/pulsedata/tiktok-ad-library-scraper): TikTok video ads shown in Europe, with run dates and audience size.
- [Website Tech Stack Detector](https://apify.com/pulsedata/website-tech-stack-detector): what an advertiser's landing pages are built with.
- [Career Page Jobs Scraper](https://apify.com/pulsedata/career-page-jobs-scraper): hiring signals for the same companies across 12 applicant tracking systems.

# Actor input Schema

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

One entry per line. Accepts advertiser names (e.g. `Salesforce`), keywords when `Match by` is set to keyword, LinkedIn Ad Library search URLs, or single ad URLs / ad IDs (e.g. `https://www.linkedin.com/ad-library/detail/1552638623`).

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

`advertiser` searches the LinkedIn page that runs the ad, `payer` the legal entity that paid for it (often the parent company - the precise way to catch every ad of a group), `keyword` searches the ad text.

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

Only ads shown in this country, as an ISO 3166-1 alpha-2 code (`us`, `de`, `gb`, …). A country name such as `Germany` is converted to its code. One country per run. Leave empty for all countries.

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

Restrict to when the ad ran. Use `custom-date-range` together with the two date fields below.

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

Format `YYYY-MM-DD`. `01.08.2026` and `08/01/2026` are converted with a warning in the log. Only used with the custom date range.

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

Format `YYYY-MM-DD`. `01.08.2026` and `08/01/2026` are converted with a warning in the log. Only used with the custom date range.

## `maxAdsPerQuery` (type: `integer`):

Stop after this many ads per advertiser or keyword. Set 0 for no limit (big advertisers run thousands of ads).

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

Opens each ad's detail page for estimated total impressions, the impression share per country, the advertiser's targeting parameters, the untruncated ad text and the destination URL. One extra request per ad. Costs $3 per 1,000 ads on top of the ad price. Until 2026-10-12 this is charged only for ads that carry impressions or targeting; from 2026-10-13 it is charged for every detail page that loads. Pages that time out or are blocked are free. Turn this off to pay only the ad price.

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

Output and charge only ads this monitor has not delivered on an earlier run. The ad IDs are remembered in a named key-value store in your account, so a scheduled daily or weekly run returns just the ads a competitor launched since the last run. Known ads cost no detail request. Ads seen before still count towards "Max ads per query", which sets how far down the list a run looks; set it to 0 to watch every ad. The first run returns everything and fills the store.

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

Name of the key-value store that remembers delivered ads. Leave empty to name it after the run's targets and filters, so each different input keeps its own history. Set a name to share one history between inputs, or to start over by picking a new one. Letters, digits and hyphens.

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

LinkedIn answers datacenter and residential proxies with a challenge page, so this actor uses the Apify Unblocker by default. Only change this if you know your proxies get through.

## Actor input object example

```json
{
  "queries": [
    "Salesforce"
  ],
  "matchBy": "advertiser",
  "countryCode": "",
  "dateOption": "",
  "maxAdsPerQuery": 100,
  "includeDetails": true,
  "onlyNewAds": false,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "UNBLOCKER"
    ]
  }
}
```

# Actor output Schema

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

LinkedIn ads stored in the default dataset (JSON/CSV/Excel via the dataset API).

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "queries": [
        "Salesforce"
    ],
    "matchBy": "advertiser",
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "UNBLOCKER"
        ]
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("pulsedata/linkedin-ad-library-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 = {
    "queries": ["Salesforce"],
    "matchBy": "advertiser",
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["UNBLOCKER"],
    },
}

# Run the Actor and wait for it to finish
run = client.actor("pulsedata/linkedin-ad-library-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 '{
  "queries": [
    "Salesforce"
  ],
  "matchBy": "advertiser",
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "UNBLOCKER"
    ]
  }
}' |
apify call pulsedata/linkedin-ad-library-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,pulsedata/linkedin-ad-library-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/LV7ZikF7IhXYjDI46/builds/zM2HStHUbqpaKq1oJ/openapi.json
