# LinkedIn Ad Library Scraper: Competitor Ads (`neverempty/linkedin-competitor-ads-scraper`) Actor

See every LinkedIn ad a competitor runs: search the LinkedIn Ad Library by company, keyword, country and date, and get ad text, images, videos, landing pages, who paid, run dates, impressions by country and targeting. No login. CSV, Excel, JSON.

- **URL**: https://apify.com/neverempty/linkedin-competitor-ads-scraper.md
- **Developed by:** [NeverEmpty](https://apify.com/neverempty) (community)
- **Categories:** Social media, Marketing, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 ad with details returneds

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?

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 Ads

**See every LinkedIn ad a competitor is running, in one spreadsheet: ad text, headline, image or video, call to action, landing page, who paid, run dates, impressions range, impressions by country and targeting, one row per ad.** Search the LinkedIn Ad Library by company name, keyword or paying company, narrow it by country, dates and impressions, and download the results as CSV, Excel or JSON. No LinkedIn login or cookies needed.

- **Type a company name, get its ads.** Put `HubSpot` or `Siemens` in **Advertiser names** and get the ads that company runs on LinkedIn, newest first.
- **Find ads on any topic.** Put words such as `CRM software` or `hiring` in **Keywords in the ad** and get ads from every advertiser that uses them.
- **See who they target.** With details on (the default), each ad also has its run dates, impressions range, impressions by country and the targeting LinkedIn shows (location, language, job, company), plus every version of the ad.
- **Quick.** In runs on 2026-10-17, 50 HubSpot ads with full details took under 2 minutes, 300 ads with details 4.5 minutes, and 1,000 ads with list data only 3.5 minutes.
- **Ready for your tools.** Download CSV, Excel or JSON, or send the rows to Google Sheets, your BI tool or an AI agent.

### Who uses it

- **Marketers and agencies** who check what competitors say, which offers they push and where their ads send people.
- **B2B sales teams** who want to know which companies are spending on LinkedIn right now.
- **Founders and product marketers** collecting ad examples before writing their own LinkedIn campaigns.
- **Researchers** who need a dated record of ads by country.

### How to use it

1. Put one or more company names in **Advertiser names** (or words in **Keywords in the ad**, or paying companies in **Paid by**).
2. Optional: pick **Countries the ads were shown in**, **When the ads ran** (or exact **Start date** / **End date**) and an impressions range.
3. Set **Ads per search** (default 50) and press **Start**. Each row is one ad; download the table when the run ends.

#### Input fields

| Field | What to put in it |
|---|---|
| `companies` | Advertiser names, one per line, such as HubSpot or Siemens |
| `keywords` | Words to find in ad text, one per line; returns ads from any advertiser |
| `payers` | The company that paid, when it differs from the brand (an agency or a parent company) |
| `searchUrls` | LinkedIn Ad Library addresses: a search results page (read with its own filters) or one ad page |
| `countries` | Two-letter country codes such as US, GB, DE; empty means all countries |
| `dateRange` | All time, Last 30 days, This month, This year or Last year |
| `startDate`, `endDate` | Exact days (YYYY-MM-DD) instead of a date range |
| `minImpressions`, `maxImpressions` | Impressions range in thousands (1000, 5000, 1000000) |
| `maxAdsPerSearch` | Ads per name, keyword or URL, 1 to 5000 (default 50) |
| `includeDetails` | On by default: adds who paid, run dates, impressions, targeting, landing page and versions. Off: only what the search list shows, at a lower price |

#### Input example

```json
{
    "companies": ["HubSpot"],
    "countries": ["US", "GB"],
    "dateRange": "last-30-days",
    "maxAdsPerSearch": 50,
    "includeDetails": true
}
```

#### Output example

```json
{
    "status": "ok",
    "adId": "1584336173",
    "adLibraryUrl": "https://www.linkedin.com/ad-library/detail/1584336173",
    "advertiserName": "HubSpot",
    "advertiserLinkedinUrl": "https://www.linkedin.com/company/68529",
    "paidBy": "HubSpot, Inc.",
    "adFormatLabel": "Single Image Ad",
    "commentary": "Was funktioniert mit KI im Unternehmensalltag wirklich? ...",
    "headline": null,
    "callToAction": null,
    "destinationUrl": null,
    "imageUrl": "https://media.licdn.com/dms/image/v2/D4D22AQEW8QyjlSCRww/feedshare-shrink_1280/...",
    "ranFrom": "2026-10-15",
    "ranTo": "2026-10-17",
    "impressions": "1k-5k",
    "impressionsMin": 1000,
    "impressionsMax": 5000,
    "targeting": [
        { "parameter": "Language", "includes": ["English"], "excludes": [], "text": "Targeting includes English" },
        { "parameter": "Location", "includes": ["DACH"], "excludes": [], "text": "Targeting includes DACH" }
    ],
    "variantCount": 1,
    "detailsIncluded": true,
    "searchType": "advertiser",
    "searchTerm": "HubSpot",
    "searchRank": 9,
    "input": "HubSpot",
    "checkedAt": "2026-10-17T15:29:07.786Z"
}
```

### Output fields

| Field | What it is |
|---|---|
| `adId`, `adLibraryUrl` | The ad's ID and its page in the LinkedIn Ad Library |
| `advertiserName`, `advertiserLinkedinId`, `advertiserLinkedinUrl`, `advertiserLogoUrl` | The company that runs the ad |
| `paidBy` | The company that paid for the ad |
| `adFormat`, `adFormatLabel` | Image, video, carousel, document, text, message, event, job or follower ad |
| `commentary`, `headline`, `callToAction`, `destinationUrl` | Ad text, headline, button text and landing page URL |
| `imageUrl`, `videoUrl`, `videoPosterUrl`, `carouselCards`, `documentTitle`, `documentPreviewImages` | The ad's media |
| `messageSender`, `messageBody` | Sender and text of message ads |
| `ranFrom`, `ranTo`, `runPeriod` | Run dates, when LinkedIn shows them |
| `impressions`, `impressionsMin`, `impressionsMax` | Impressions range, as text and as numbers |
| `impressionsByCountry` | Share of impressions per country |
| `targeting`, `targetingParameters` | What the ad targets or excludes (location, language, job, company and more) |
| `variantCount`, `variants` | Every version of the ad |
| `searchType`, `searchTerm`, `searchUrl`, `searchRank`, `input` | Which of your inputs produced the row, and the ad's place in the list |
| `status`, `note` | `ok` for an ad row; other values are free rows that say why nothing was returned |

### FAQ

#### How do I see all the LinkedIn ads a competitor is running?

Put the competitor's name in **Advertiser names** and run it: you get one row per ad with text, image or video, landing page, run dates and impressions. Set **Ads per search** to the number you want (up to 5,000) and pick a date range to see only recent ads.

#### How do I export the LinkedIn Ad Library to CSV or Excel?

Run a search here and download the table as CSV or Excel from the run's **Output** tab. Every ad is one row, and the ad's Ad Library link is in `adLibraryUrl` so you can open the original.

#### How do I find LinkedIn ads by keyword?

Put the words in **Keywords in the ad**, one per line, such as `CRM software` or `cybersecurity`. Each keyword returns ads from any advertiser that uses it; add countries or a date range to narrow it.

#### Can I see the targeting and impressions of a competitor's LinkedIn ads?

Yes, keep **Include full ad details** on and each ad gets the impressions range, impressions by country and the targeting LinkedIn shows (location, language, job, company). LinkedIn shows these for part of the ads only (see Limitations).

#### How do I track new LinkedIn ads from competitors every week?

Save your input as a task and schedule it weekly with **When the ads ran** set to Last 30 days or exact start and end dates. Each row has `checkedAt` and `adId`, so you can keep only the ads you have not seen before.

### Pricing

You pay per ad returned, plus a small fee per run. Ads with details cost more than ads with only the search list data; turn **Include full ad details** off to pay the lower price. Free rows (a search with no ads, an ad that is gone, a refused request) are never charged, and the same ad found by two of your searches is charged once. Set a maximum total charge for a run and the Actor stops before going over it and adds a free row that says so. If a run gets close to its Timeout, it stops on its own the same way: every ad returned is kept and charged, nothing after that is read or charged, and a free row says so.

### Limitations

- Data comes from the public LinkedIn Ad Library, which keeps ads for one year. Older ads are not available.
- Run dates, impressions, impressions by country and targeting appear only when LinkedIn shows them on the ad's page. In runs on 2026-10-17 that was 22% to 55% of ads; the other ads come back with these fields empty. Nothing is guessed.
- Spend, clicks and results are not in the Ad Library, so they are not included.
- `headline`, `callToAction` and `destinationUrl` are empty for ads that have none (many text and thought-leader posts).
- A company name search returns the ads LinkedIn lists for that advertiser name. For a company whose name is shared by others, add countries or paste the Ad Library search URL you already checked.
- With **Include full ad details** off, rows have only the search list data: advertiser, format, ad text, headline and image.
- Up to 500 names and keywords and 5,000 URLs per run, and up to 5,000 ads per search. How many fit in one run depends on its Timeout (1 hour by default): in runs on 2026-10-17 that was about 70 ads a minute with details (about 3,500 an hour) and about 300 a minute per search with list data only. For more, raise the Timeout in the run options, turn off **Include full ad details**, or split the input into several runs.

### Thanks for using this Actor

We build these tools for people who run them every day, and we improve them from what users tell us.

- **Missing a field, or need another filter?** Tell us in the **Issues** tab. If the data is there, we add it.
- **Found a bug or a wrong value?** Post the run ID in the **Issues** tab. Wrong data is the thing we fix first.

If this Actor saved you time, a short review helps other people find it.

# Actor input Schema

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

Company names whose LinkedIn ads you want, one per line, such as HubSpot or Siemens. Each name is searched as the advertiser in LinkedIn's Ad Library and returns that company's ads. If every search list is empty, the example advertiser HubSpot is read.

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

Words to find in ad text, one per line, such as hiring or CRM software. Each keyword returns the ads from any advertiser that use it.

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

The company that paid for the ads, one per line, when it differs from the brand name (for example an agency or a parent company).

## `searchUrls` (type: `array`):

Paste LinkedIn Ad Library addresses, one per line: a search results page (https://www.linkedin.com/ad-library/search?accountOwner=HubSpot\&countries=US) is read with its own filters, and one ad (https://www.linkedin.com/ad-library/detail/970353693) returns that ad.

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

Two-letter country codes, one per line, such as US, GB or DE. Empty means all countries.

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

Pick a period, or leave empty for all time (LinkedIn keeps ads for one year). To set exact days, use Start date and End date instead.

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

Only ads that ran on or after this day (YYYY-MM-DD). The earliest is one year before today.

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

Only ads that ran on or before this day (YYYY-MM-DD). Empty means today.

## `minImpressions` (type: `integer`):

Only ads with at least this many impressions, in thousands (1000, 5000, 1000000).

## `maxImpressions` (type: `integer`):

Only ads with at most this many impressions, in thousands (1000, 5000, 1000000).

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

How many ads to return for each name, keyword or search URL, newest first as LinkedIn lists them (1 to 5000). Empty means 50. With details, a run reads about 70 ads a minute, so the default 1-hour Timeout fits about 3,500; raise the Timeout for more.

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

On by default: each ad also gets its detail page: who paid, run dates, impressions range, impressions by country, targeting (location, language, job, company), call to action, landing page URL, video and every version of the ad. Off: only what the search list shows (advertiser, format, ad text, headline, image), at a lower price per ad.

## Actor input object example

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

# Actor output Schema

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

One row per LinkedIn ad: ad ID and Ad Library link, advertiser, who paid, format, ad text, headline, call to action, landing page URL, image or video, run dates, impressions range, impressions by country, targeting and every version of the ad. A search with no ads, an ad that is gone, a refused request or a run that hit its maximum charge comes back as a free row that says why.

# 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": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("neverempty/linkedin-competitor-ads-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 = {
    "companies": ["HubSpot"],
    "maxAdsPerSearch": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("neverempty/linkedin-competitor-ads-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 '{
  "companies": [
    "HubSpot"
  ],
  "maxAdsPerSearch": 50
}' |
apify call neverempty/linkedin-competitor-ads-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,neverempty/linkedin-competitor-ads-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/JnHRaaQH2kMnFeoPi/builds/Ih53gBWHgcedxj7V4/openapi.json
