# LinkedIn Ad Library Scraper: Ads, Impressions and Targeting (`oski/linkedin-ad-library-scraper`) Actor

Export LinkedIn ads by advertiser, keyword, company or country: ad copy, images, videos, carousels and documents, who paid for each ad and when it ran. For EU ads you also get impressions by country and the targeting used. No login, no cookies.

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

## Pricing

from $1.45 / 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.
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

Scrape the LinkedIn Ad Library into clean, structured data. Search by advertiser name, keyword, LinkedIn company ID or a pasted Ad Library URL, filter by country and date, and get every ad's copy, media, paying entity, run dates, impressions (total and by country) and targeting. On a real 180-ad test run, every ad LinkedIn could load came back with full details, and 1,000 ads cost about $1.50.

No login, no cookies, no LinkedIn account at risk. Part of an ad-intelligence suite with the [Google Ads Transparency Center Scraper](https://apify.com/oski/google-ads-transparency-scraper) and the [Facebook Ad Library Scraper](https://apify.com/oski/facebook-ad-library-scraper), so you can cover LinkedIn, Google and Meta from one account.

### What you can use it for

- **B2B agencies:** pull a prospect's and their competitors' LinkedIn ads before a pitch, with copy, formats and who paid.
- **SaaS founders and product marketers:** see how competitors position new features, which formats they favour and when campaigns start and stop.
- **Demand generation teams:** read the targeting block (languages, locations, and whether company, job or audience targeting was used) on EU-covered ads.
- **Creative teams:** build a swipe file of carousels, document ads, videos and message ads with the full text.
- **Recruiters and employer-brand teams:** track which companies are advertising roles and culture on LinkedIn, and in which countries.

### How to use it

1. Under **Advertiser names**, add companies as they appear on LinkedIn, such as `HubSpot`. Or search ad text with **Keywords**.
2. Optionally add **Countries** (names or codes) and a date range with the date pickers.
3. Leave **Max ads per search** at 1,000 (or change it) and click **Start**.
4. When it finishes, open the **Output** tab and download as CSV, Excel or JSON.

While it runs, the status line at the top of the run page shows progress (for example "Search 1 of 2: advertiser HubSpot. 120 ads so far."). When it finishes, the same line says what was found and explains anything that was skipped, so you never have to read the log.

### How much does it cost?

You pay a $0.002 start fee plus $0.0015 per ad returned. Ads LinkedIn cannot load are still listed for transparency but never charged, so you only pay for ads that deliver data.

| Ads returned | Maths | Cost |
|---|---|---|
| 100 | $0.002 + 100 x $0.0015 | about $0.15 |
| 1,000 | $0.002 + 1,000 x $0.0015 | about $1.50 |
| 10,000 | $0.002 + 10,000 x $0.0015 | about $15.00 |

A typical 1,000-ad export of one competitor set costs about $1.50. Turning off `includeDetails` gives a faster preview-only scrape at the same per-ad price.

### Input

| Field | What it does |
|---|---|
| `advertisers` | Advertiser names as shown on LinkedIn (`hubspot`). One search each. |
| `keywords` | Words in the ad text (`payroll software`). One search each. |
| `companyIds` | Exact advertiser match by LinkedIn company number (`68529`) or its linkedin.com/company/68529 link. |
| `searchUrls` | Any `linkedin.com/ad-library/search?...` URL, with its filters used as-is. |
| `adUrls` | Specific ads by detail URL or numeric ID. |
| `countries` | Only ads shown in these countries: names or codes (`United Kingdom`, `UK`, `US`). Empty means all. |
| `dateRange` | `all`, `last-30-days`, `current-month`, `current-year`, `last-year`. |
| `startDate` / `endDate` | Custom range from the date pickers, or relative dates such as 3 months. Either one alone works: the other end is filled in. LinkedIn's data runs a day behind, so today counts as yesterday. Overrides `dateRange`. |
| `includeDetails` | On by default. Off gives a faster preview-only scrape (advertiser, format, copy, headline, image). |
| `maxAdsPerSearch` | Cap per search (default 1,000). |
| `maxAds` | Cap for the whole run (0 means no cap). |
| `proxyConfiguration` | Advanced. Apify Proxy; the default datacenter proxy works. |

Example for a CRM competitor sweep:

```json
{
  "advertisers": ["hubspot", "salesforce", "pipedrive"],
  "countries": ["GB", "DE", "FR"],
  "dateRange": "current-year",
  "maxAdsPerSearch": 400,
  "maxAds": 1000
}
```

### Output

One row per ad, always with the same fields in the same order:

```json
{
  "ad_id": "1560106944",
  "ad_url": "https://www.linkedin.com/ad-library/detail/1560106944",
  "search": "advertiser:hubspot",
  "details_status": "ok",
  "advertiser_name": "HubSpot",
  "advertiser_type": "company",
  "advertiser_company_id": "68529",
  "advertiser_url": "https://www.linkedin.com/company/68529",
  "paid_by": "HubSpot, Inc.",
  "format": "image",
  "format_label": "Single Image Ad",
  "creative_type": "SPONSORED_STATUS_UPDATE",
  "headline": "Sell from anywhere, close everywhere.",
  "commentary": "Remote-first and built on trust.\nSell from wherever you're sharpest, on your own rhythm. The results are what matter here.",
  "cta": "Learn more",
  "image_urls": ["https://media.licdn.com/dms/image/v2/..."],
  "video_urls": [],
  "carousel_slides": [],
  "document_page_urls": [],
  "has_eu_transparency_data": true,
  "first_shown": "2026-09-14",
  "last_shown": "2026-09-17",
  "impressions_range": "30k-50k",
  "impressions_min": 30000,
  "impressions_max": 50000,
  "impressions_by_country": [],
  "targeting": {
    "language": {"includes": ["English"], "excludes": []},
    "location": {
      "includes": ["Germany", "Boston, MA", "Toronto, ON", "Australia", "Ireland", "United Kingdom"],
      "excludes": ["India", "United Arab Emirates"]
    }
  },
  "targeting_parameters": [
    {"parameter": "Company", "targeted": true, "excluded": true},
    {"parameter": "Job", "targeted": true, "excluded": false}
  ],
  "scraped_at": "2026-09-17T14:25:02+00:00"
}
```

Every format is parsed: single image, video (each MP4 rendition with its bitrate, plus `video_poster_url`), carousel (each slide's title and image), document (every page image), message ads (`message_sender`, `message_body`, `message_ctas`), article, follower and event ads. Rows also carry `advertiser_logo_url` and `commentary_links`.

Impression ranges become real numbers (`30k-50k` gives `30000` and `50000`). LinkedIn joins targeted places with commas ("Boston, MA, New York, United States"), so the actor splits them back into real places. Ads LinkedIn cannot load come back with `details_status: "unavailable"` and are not charged.

**Dates, impressions and targeting only exist for some ads.** LinkedIn publishes them for ads covered by the EU Digital Services Act; other ads show the creative and payer only. `has_eu_transparency_data` tells you which is which. Impressions by country can take up to 48 hours to appear for new ads, and LinkedIn keeps about 12 months of ads in the library.

### Tips for bigger runs

- **Add more advertisers, keywords or company IDs.** Each is its own search with its own `maxAdsPerSearch` budget, and an ad found by two searches is only returned (and charged) once.
- **Use `maxAds` as a spending ceiling** when you run many searches at once.
- **Use company IDs** when several advertisers share a name. They match one advertiser exactly.
- **Turn off `includeDetails` for volume scans.** Preview mode skips the detail page per ad, so it is much faster; switch it back on for dates, impressions and targeting.
- **Paste a search URL** to use LinkedIn filters the input form does not expose, such as impression ranges.
- **Download media you want to keep.** LinkedIn CDN links can expire after a while.

### Scheduling and integrations

Save your input as a task and schedule it weekly in Apify to track new campaigns. Results can go straight to Google Sheets, a webhook, Make, Zapier or your own code via the Apify API:

```bash
curl -X POST "https://api.apify.com/v2/acts/oski~linkedin-ad-library-scraper/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"advertisers": ["hubspot"], "countries": ["GB"], "maxAdsPerSearch": 100}'
```

With full details the actor reads about one ad per second, so the sync endpoint (which waits up to 5 minutes) suits runs of a couple of hundred ads. For bigger runs, start the actor with the `runs` endpoint and fetch the dataset when it finishes.

### If something looks wrong

The status line at the end of every run explains anything that was skipped or came back empty. The common messages:

| Message | What to do |
|---|---|
| Input problem: ... | Something in the input needs changing, and the message says what. The run stopped before anything was charged. |
| No ads for advertiser ... | Check the name as LinkedIn shows it, or use the company number. Widen dates or countries. |
| ... could not be loaded | LinkedIn was busy. Run it again later. |
| Skipped countries: ... | The country was not recognised. Check the spelling or use a two-letter code. |
| N ads were no longer available | Those ads were removed before they could be opened. They are not charged. |

Still stuck? Open an issue on the actor page with the run link and it gets looked at quickly.

### FAQ

**Is it legal?** It reads only the public LinkedIn Ad Library, which LinkedIn publishes for transparency. No login, no cookies and no private data are involved. You are responsible for how you use the data, including respecting privacy law where it applies.

**Do I need a LinkedIn account?** No. Nothing is logged in, so no account can be restricted.

**Why did I get fewer ads than expected?** The advertiser may run fewer ads in those countries or dates, the name may match a different advertiser (use a company ID), or a cap was hit. Searches that find nothing are named in the run log.

**Why are dates and impressions empty on some ads?** LinkedIn only publishes them for ads covered by the EU Digital Services Act. Check `has_eu_transparency_data`.

**How fresh is the data?** Every run reads the Ad Library live, so you get what LinkedIn shows at that moment.

**What if it breaks?** Open an issue on the actor page. It gets fixed fast.

### Other scrapers from Oski

- [Google Ads Transparency Center Scraper](https://apify.com/oski/google-ads-transparency-scraper): every Google ad an advertiser runs, with impressions and dates by country.
- [Facebook Ad Library Scraper](https://apify.com/oski/facebook-ad-library-scraper): Facebook and Instagram ads by keyword or advertiser Page ID.
- [UK Decision Maker Finder](https://apify.com/oski/uk-decision-maker-finder): named directors behind UK company websites.
- [Website Contact Finder](https://apify.com/oski/website-contact-finder): emails, phones and socials from any list of company websites.

# Actor input Schema

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

Companies whose LinkedIn ads you want, one per line, as their name appears on LinkedIn: HubSpot, Salesforce. This is the easiest way to start.

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

Search ad text for these keywords, e.g. crm or payroll software. Each keyword is a separate search.

## `companyIds` (type: `array`):

Optional, for an exact match: the number in a LinkedIn company link, such as 68529 from linkedin.com/company/68529. Pasting that link works too. Use Advertiser names if you don't know the number.

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

Paste any linkedin.com/ad-library/search URL with filters already set on the website. Its own filters are used as-is.

## `adUrls` (type: `array`):

Fetch specific ads directly by their Ad Library detail URL (linkedin.com/ad-library/detail/1560106944) or bare numeric ID.

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

Only ads shown in these countries, one per line. Names or codes both work: United Kingdom, UK, US, Germany. Leave empty for all countries.

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

Only ads that ran in this period. Ignored when a start or end date is set.

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

Only ads running on or after this date. Pick a date or a relative one such as 3 months. LinkedIn keeps about the last 12 months. Overrides Date range.

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

Only ads running on or before this date. Leave empty for up to today.

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

Open each ad's detail page for full ad copy, video files, carousel slides, document pages, paying entity, run dates, impressions by country and targeting. Turn off for a faster preview-only scrape.

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

Stop each advertiser, keyword, company or URL search after this many ads. About $1.50 for 1,000 ads. Runs take about a second per ad with details, so 1,000 ads is roughly 15 minutes.

## `maxAds` (type: `integer`):

Optional cap on ads across the whole run, to control cost. Leave empty or 0 for no cap.

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

Apify Proxy rotates IPs when LinkedIn rate limits a request. The default works for most runs.

## Actor input object example

```json
{
  "advertisers": [
    "hubspot",
    "salesforce"
  ],
  "keywords": [
    "crm"
  ],
  "companyIds": [
    "68529"
  ],
  "searchUrls": [
    "https://www.linkedin.com/ad-library/search?accountOwner=hubspot&countries=GB"
  ],
  "adUrls": [
    "https://www.linkedin.com/ad-library/detail/1560106944"
  ],
  "countries": [
    "GB",
    "US"
  ],
  "dateRange": "all",
  "includeDetails": true,
  "maxAdsPerSearch": 200,
  "maxAds": 0,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

One row per ad: advertiser, format, headline, ad copy, button, run dates, impressions and link.

## `allFields` (type: `string`):

Every field for each ad, including video files, carousel slides, impressions by country and targeting, as raw JSON.

# 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": [
        "hubspot"
    ],
    "maxAdsPerSearch": 200,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("oski/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 = {
    "advertisers": ["hubspot"],
    "maxAdsPerSearch": 200,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("oski/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 '{
  "advertisers": [
    "hubspot"
  ],
  "maxAdsPerSearch": 200,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call oski/linkedin-ad-library-scraper --silent --output-dataset

```

## MCP server setup

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