# LinkedIn Ad Library Scraper — Ads, Creatives & Advertisers (`jaybird/linkedin-ad-library-scraper`) Actor

Scrape LinkedIn's Ad Library into clean JSON: ad creatives, copy, CTA, advertiser & payer, EU impressions and targeting. Search by company or keyword. $4 per 1,000 ads.

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

## Pricing

from $0.35 / 1,000 basic ads

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/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## LinkedIn Ad Library Scraper

Scrape [LinkedIn's public Ad Library](https://www.linkedin.com/ad-library/home) into clean JSON without an account, cookies, or API approval. Search by advertiser, paying entity, or keyword; filter by country, date, impressions, and targeting category; then choose the price/detail level that fits the job.

- **Basic — $0.50 per 1,000 ads:** card-visible data with no per-ad request; long copy can be truncated by LinkedIn.
- **Enriched — $1.50 per 1,000 ads:** basic data plus one detail-page request per ad for payer, advertiser URL, and EU transparency fields.

Basic is the default (lower cost, no per-ad requests); choose enriched when you need payer, advertiser URL, or EU transparency fields. Every output row is tagged with `detailLevel`.

```json
{
    "type": "ad",
    "detailLevel": "enriched",
    "adId": "1462877434",
    "adUrl": "https://www.linkedin.com/ad-library/detail/1462877434",
    "advertiserName": "Example Company",
    "advertiserLogoUrl": "https://media.licdn.com/dms/image/...",
    "advertiserLinkedInUrl": "https://www.linkedin.com/company/123456",
    "payer": "Example Holdings",
    "format": "single_image",
    "headline": "See what's new",
    "adText": "Explore the latest guide from Example Company.",
    "ctaText": "Learn more",
    "imageUrls": ["https://media.licdn.com/dms/image/..."],
    "landingPageUrl": "https://www.example.com/campaign",
    "firstShownDate": "2026-07-01",
    "lastShownDate": "2026-07-12",
    "impressionsRange": "10k-50k",
    "impressionsByCountry": [{ "country": "Netherlands", "share": "100%" }],
    "targeting": {
        "language": { "included": ["English"], "excluded": [] },
        "location": { "included": ["Netherlands"], "excluded": [] }
    },
    "query": "Example Company",
    "queryType": "company",
    "countries": ["NL"]
}
```

### Why use it?

- **Low-cost bulk discovery:** Basic mode reads about 24 complete ad cards from each server-rendered search page. There is no browser and no per-ad fetch.
- **Optional enrichment:** Pay for detail requests only when you need payer, exact advertiser URL, or EU transparency data.
- **Employer-brand ads included:** Company discovery and employer-brand campaigns use LinkedIn's separate public endpoint; both their search cards and detail pages are supported.
- **No account risk:** The Actor only accesses LinkedIn's public, logged-out transparency pages. It never asks for or uses credentials.
- **Useful creative data:** Card-visible copy, headline, advertiser logo, images, video, poster, and format; enriched mode adds full copy, CTA, and the disclosed landing page where available.
- **Honest completeness:** Blocked or changed pages fail visibly. The run summary distinguishes true zero-result searches from incomplete work.

### Basic vs. enriched

| Field | Basic | Enriched |
|---|---:|---:|
| Ad ID, permalink, advertiser name/logo, format | ✓ | ✓ |
| Headline, images/video, advertiser logo | ✓ | ✓ |
| Full copy, CTA, landing page | Best effort; card text may be truncated | ✓ |
| Advertiser LinkedIn URL and payer | — | ✓ |
| First/last shown dates | — | EU-shown ads only |
| Impression range and country shares | — | EU-shown ads only |
| Targeting details | — | EU-shown ads only |
| LinkedIn requests for 1,000 ads | About 42 search pages | About 42 search pages + 1,000 detail pages |
| FREE-tier event price | $0.50 / 1,000 | $1.50 / 1,000 |

LinkedIn discloses dates, impressions, and targeting only for ads shown in the EU under the Digital Services Act. Non-EU detail pages do not contain those fields; the Actor leaves them empty rather than inventing data.

### Inputs

| Input | Description |
|---|---|
| `detailLevel` | `basic` (default) or `enriched` |
| `companies` | Advertiser names or slug-based LinkedIn company URLs; each becomes a query |
| `payer` | Search the public "Paid for by" field as another query |
| `keyword` | Search ad copy as another query |
| `countries` | Repeated ISO alpha-2 country filters, e.g. `US`, `DE`, `NL` |
| `startDate`, `endDate` | Custom date range in `YYYY-MM-DD` format |
| `impressionsMinValue`, `impressionsMinUnit` | Lower impression threshold using `none`, `thousand`, or `million` |
| `impressionsMaxValue`, `impressionsMaxUnit` | Upper impression threshold |
| `includedTargetingFacetCategories` | Require disclosed categories such as `LOCATION`, `COMPANY`, or `JOB` |
| `excludedTargetingFacetCategories` | Require categories with excluded values |
| `sortBy` | `DESCENDING`, `ASCENDING`, or `MOST_IMPRESSIONS` |
| `maxAdsPerQuery` | Cap per advertiser, payer, or keyword query; default 100, maximum 5,000 |

```json
{
    "detailLevel": "basic",
    "companies": ["HubSpot", "https://www.linkedin.com/company/salesforce"],
    "payer": "Example Holdings",
    "keyword": "crm software",
    "countries": ["DE", "NL"],
    "startDate": "2026-01-01",
    "endDate": "2026-07-01",
    "impressionsMinValue": 10,
    "impressionsMinUnit": "thousand",
    "includedTargetingFacetCategories": ["LOCATION", "JOB"],
    "sortBy": "MOST_IMPRESSIONS",
    "maxAdsPerQuery": 100
}
```

If no company, payer, or keyword is supplied, the platform auto-test runs a small HubSpot demo capped at ten ads.

### Output

The default dataset contains ads only, so JSON, CSV, and Excel exports stay homogeneous. The `OUTPUT` key-value record holds diagnostics and one summary per query:

```json
{
    "detailLevel": "basic",
    "summaries": [
        {
            "type": "advertiser-summary",
            "detailLevel": "basic",
            "query": "HubSpot",
            "queryType": "company",
            "totalAdsFound": 2285,
            "adsScraped": 100,
            "adsFailed": 0,
            "dateRangeCovered": null,
            "blocked": false,
            "incomplete": false
        }
    ],
    "adsScraped": 100,
    "escalatedToResidential": 0,
    "chargeLimitReached": false,
    "charged": {
        "basic-ad-scraped": 100,
        "enriched-ad-scraped": 0
    }
}
```

Local and non-pay-per-event runs report zero custom events even though they still write the dataset.

### Pricing

Each stored ad emits exactly one mutually exclusive event:

- `basic-ad-scraped`: **$0.0005** per ad, or **$0.50 per 1,000** at the FREE tier.
- `enriched-ad-scraped`: **$0.0015** per ad, or **$1.50 per 1,000** at the FREE tier.
- `apify-actor-start`: **$0.00005** once per run, emitted automatically by Apify.

Higher Apify pricing tiers receive lower per-ad rates. Failed requests, duplicate ads, true zero-result searches, and summary records do not emit ad events. The Pricing tab is authoritative for the live rates.

### How it works

LinkedIn's logged-out Ad Library is server-rendered:

1. `GET /ad-library/search` returns roughly 24 complete ad cards.
2. A pagination token inside an HTML comment leads to `/searchPaginationFragment`.
3. Basic mode stores those cards immediately.
4. Enriched mode also requests `/detail/{adId}` once per unique standard ad, or `/employer-brand/{id}` for LinkedIn's employer-brand formats.

There is no headless browser. Requests begin on datacenter proxies with rotating sessions; repeatedly blocked requests can escalate to residential proxies within a per-run budget. LinkedIn's status `999`, login redirects, missing page markers, and markup failures are treated as errors instead of empty results.

### FAQ

**Is there a public LinkedIn ads API or a Google-style batch endpoint?** LinkedIn does not expose one on the logged-out Ad Library. Its own page JavaScript paginates by fetching server-rendered HTML fragments. Enriched-only fields still require one public detail request per ad. We did find and support LinkedIn's separate `/employer-brand/{id}` detail route, but it is also per-ad—not a batch API. Basic mode is the cheap endpoint: the search fragment already contains most card-visible creative fields for about 24 ads at a time, although LinkedIn can truncate long copy there.

**Why do some enriched ads have no dates, impressions, or targeting?** LinkedIn only publishes those fields for ads shown in the EU. Payer and advertiser details can still be present.

**How exact is advertiser matching?** LinkedIn uses a loose name match, so `HubSpot` can return agencies with HubSpot in their name. Use enriched mode's `advertiserLinkedInUrl` for exact post-filtering. Numeric `/company/12345` URLs cannot be resolved to a name while logged out; supply a name or slug URL.

**How far back does data go?** LinkedIn says ads remain in the Ad Library for one year after their last impression.

**Can I schedule or integrate it?** Yes. Export to Google Sheets, Drive, S3, or a database; use Zapier, Make, webhooks, schedules, or the Apify API.

Monitoring competitors across channels? See the [Meta Ad Library Scraper](https://apify.com/jaybird/meta-ad-library-scraper) and [Google Ads Transparency Scraper](https://apify.com/jaybird/google-ads-transparency-scraper).

The Actor collects public advertising-transparency data only. You remain responsible for lawful use of the output.

# Actor input Schema

## `detailLevel` (type: `string`):

Basic (default) returns card-visible fields with no per-ad request at $0.50 / 1,000 ads; LinkedIn may truncate long card copy. Enriched visits every ad detail page for full copy/CTA/landing page where disclosed, advertiser URL, payer, and EU transparency fields at $1.50 / 1,000 ads.

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

Company names (e.g. "HubSpot") or LinkedIn company-page URLs (e.g. https://www.linkedin.com/company/hubspot). Each entry becomes one advertiser search. LinkedIn matches advertiser names loosely, so a search for "HubSpot" also returns other advertisers with HubSpot in their name — every result carries `advertiserName` and `advertiserLinkedInUrl` so you can filter exactly.

## `keyword` (type: `string`):

Free-text search across ad copy. Runs as its own query in addition to any company searches.

## `payer` (type: `string`):

Search the public 'Paid for by' field. Runs as its own query in addition to company and keyword searches.

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

Two-letter ISO 3166-1 country codes to filter where ads were shown, e.g. \["US", "DE", "NL"]. Leave empty for worldwide. Note: ads shown in the EU expose extra transparency data (run dates, impressions, targeting) that other ads do not.

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

Only return ads shown on or after this date (YYYY-MM-DD). The Ad Library holds ads for one year after their last impression.

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

Only return ads shown on or before this date (YYYY-MM-DD).

## `impressionsMinValue` (type: `integer`):

Minimum impression threshold. Combine with the minimum unit; LinkedIn applies this to ads with disclosed impression data.

## `impressionsMinUnit` (type: `string`):

Scale used for the minimum impression value.

## `impressionsMaxValue` (type: `integer`):

Maximum impression threshold. Combine with the maximum unit.

## `impressionsMaxUnit` (type: `string`):

Scale used for the maximum impression value.

## `includedTargetingFacetCategories` (type: `array`):

Only return ads that include these disclosed targeting-category types. Allowed: LANGUAGE, LOCATION, AUDIENCE, DEMOGRAPHIC, COMPANY, EDUCATION, JOB, INTERESTS\_AND\_TRAITS.

## `excludedTargetingFacetCategories` (type: `array`):

Only return ads that explicitly exclude values in these targeting-category types. Allowed: LANGUAGE, LOCATION, AUDIENCE, DEMOGRAPHIC, COMPANY, EDUCATION, JOB, INTERESTS\_AND\_TRAITS.

## `sortBy` (type: `string`):

Order applied by LinkedIn before pagination.

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

Cap per advertiser, payer, or keyword query. Each stored ad emits exactly one basic or enriched billing event.

## Actor input object example

```json
{
  "detailLevel": "basic",
  "companies": [
    "HubSpot"
  ],
  "keyword": "crm software",
  "payer": "Example Holdings",
  "countries": [
    "NL",
    "DE"
  ],
  "startDate": "2026-01-01",
  "endDate": "2026-07-01",
  "impressionsMinValue": 10,
  "impressionsMinUnit": "thousand",
  "impressionsMaxValue": 2,
  "impressionsMaxUnit": "thousand",
  "sortBy": "DESCENDING",
  "maxAdsPerQuery": 100
}
```

# Actor output Schema

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

Default dataset containing one homogeneous record per scraped ad, tagged with its basic or enriched detail level.

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

Per-query summaries, request health, and confirmed custom-event counts from the OUTPUT record.

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

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

# Run the Actor and wait for it to finish
run = client.actor("jaybird/linkedin-ad-library-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).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"
  ]
}' |
apify call jaybird/linkedin-ad-library-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=jaybird/linkedin-ad-library-scraper",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/OcnIwhC2FOF5w8dnh/builds/GmaracfxFOO3a3FEJ/openapi.json
