# LinkedIn Ads Scraper - Ad Library Data, $0.25 per 1,000 Ads (`dami_studio/linkedin-ads-scraper`) Actor

The company an ad is for and the company paying for it are not always the same. The LinkedIn Ad Library names both. Search either, plus keyword and country. One row per ad: copy, headline, CTA, creative, landing URL, format, dates and impressions where shown. $0.25/1,000 ads.

- **URL**: https://apify.com/dami\_studio/linkedin-ads-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Social media, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.25 / 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/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 Ads Scraper

The company an ad is for and the company that paid for it are not always the same one, and the public LinkedIn Ad Library names both. Company names, LinkedIn company URLs, keywords, payer names and country codes all search it, and you get one row per ad: the advertiser, the payer, the full ad copy, the headline and call to action, the image or video creative, the landing URL with its tracking parameters intact, the ad format, and the dates and countries LinkedIn discloses.

No account, no cookies, no browser. It reads the library the same way a logged-out visitor does.

### What you get

One row per ad. This is a real row, trimmed only where noted:

```json
{
  "searchTerm": "HubSpot",
  "searchType": "company",
  "adId": "1478337423",
  "adUrl": "https://www.linkedin.com/ad-library/detail/1478337423",
  "advertiserName": "HubSpot",
  "advertiserUrl": "https://www.linkedin.com/company/68529",
  "advertiserId": "68529",
  "payerName": "HubSpot, Inc.",
  "adType": "Single Image Ad",
  "creativeType": "SPONSORED_STATUS_UPDATE",
  "adText": "Brand consistency drives revenue growth for 68% of organizations. Learn how HubSpot & Canva help you build a brand that sticks.",
  "headline": "Get The Free Brand Consistency Guide From HubSpot & Canva",
  "callToAction": "Learn more",
  "landingUrl": "https://offers.hubspot.com/how-to-build-a-brand-canva?utm_campaign=Strategic+Partnerships&utm_source=linkedin&utm_medium=paid&hsa_acc=517582366&hsa_cam=1173415993&hsa_ad=1478337423",
  "imageUrl": "https://media.licdn.com/dms/image/v2/D4D10AQHeILHUGtZ8-A/image-shrink_1280/...",
  "videoUrl": null,
  "firstShownDate": "2026-08-13",
  "lastShownDate": "2026-08-19",
  "runDates": "Ran from Aug 13, 2026 to Aug 19, 2026",
  "totalImpressions": "1k-5k",
  "countries": ["United Kingdom", "Canada", "Australia", "United States", "New Zealand", "Ireland"],
  "impressionsByCountry": [
    { "country": "United Kingdom", "impressionsPercent": 32 },
    { "country": "Canada", "impressionsPercent": 22 }
  ],
  "targeting": [
    { "facet": "Language", "value": "Targeting includes English" },
    { "facet": "Location", "value": "Targeting includes Australia and New Zealand, United States, Canada, Ireland, United Kingdom" }
  ],
  "isRestricted": false,
  "restrictionDetails": null,
  "scrapedAt": "2026-08-20T04:46:55.613Z"
}
```

The landing URL keeps its UTM and `hsa_` parameters. That's usually the most useful field in the row, because it tells you which campaign and ad group the creative belongs to.

### Run dates, impressions and targeting are EU-only

`runDates`, `totalImpressions`, `impressionsByCountry` and `targeting` are there only when LinkedIn discloses them, and LinkedIn discloses them only for ads delivered into the EU. That's the Digital Services Act, not a choice we made. A US-only campaign comes back with those four fields null and everything else populated.

So put an EU country code in `countries`. If the advertiser runs anything at all into the EU, you get the dates, the impressions band and the targeting for those ads.

### Input

Search by company:

```json
{
  "companies": ["HubSpot", "https://www.linkedin.com/company/salesforce"],
  "maxItems": 200,
  "includeAdDetails": true
}
```

Sweep an EU country to get the disclosure fields:

```json
{
  "keywords": ["marketing automation"],
  "countries": ["DE", "FR"],
  "dateRange": "last-30-days",
  "maxItems": 500
}
```

Map an agency's client roster by who paid:

```json
{
  "payers": ["Ogilvy"],
  "maxItems": 300
}
```

- `companies`, `keywords`, `payers`, `countries` — up to 20 entries each. They all apply to the same run.
- `maxItems` — total rows across every search, not per search. The budget splits evenly, so four searches at 200 gives you fifty of each. Default 20, ceiling 2,000. Keep it low while you're testing.
- `includeAdDetails` — on by default, and you want it on. It's the only place LinkedIn publishes the untruncated ad copy, the landing URL, the run dates, the impressions band and the targeting. Turning it off gives a faster advertiser-and-creative sweep. The price per ad is identical either way, so there's rarely a reason.
- `dateRange` — one of `all`, `last-30-days`, `current-month`, `current-year`, `last-year`. Spell it exactly like that; anything else is treated as `all`.
- `dateFrom` / `dateTo` — exact window, `YYYY-MM-DD`. LinkedIn only accepts dates inside the last twelve months.

A company **name** matches on the advertiser name and can catch more than one page with a similar name. A company **URL with a numeric id** filters to exactly that advertiser. Use the URL when precision matters.

### Other limits

- The library holds roughly the last twelve months. Anything older isn't there to fetch.
- `totalImpressions` is a band (`"1k-5k"`), never an exact number. LinkedIn publishes it that way.
- Some ads come back `isRestricted: true` with the creative withheld. `restrictionDetails` says why.
- `videoUrl` is null on image ads, and `imageUrl` is null on video ads. Check `adType`.
- Searching by name can return an advertiser you didn't mean. `advertiserId` in the output tells you which page you actually got.

### Billing

$0.25 per 1,000 ads, plus a $0.001 run-start fee. Same rate on every plan, free or paid, with no volume tiers and no minimum spend.

You pay per ad written to your dataset. Not charged: sample rows, duplicate ads, searches that matched nothing, and ads that couldn't be read.

Because `maxItems` is a hard total, your worst case for a run is exactly `maxItems × $0.00025` plus the start fee. A 2,000-ad run costs 50 cents.

# Actor input Schema

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

Company names as they appear on LinkedIn ("HubSpot"), or LinkedIn company URLs (https://www.linkedin.com/company/hubspot). Up to 20 per run. A URL with a numeric id filters exactly; a name or a vanity URL matches the advertiser name, which can catch more than one page.

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

Words or phrases searched inside the text of the ad. Several words in one entry are combined with AND. Up to 20 per run.

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

The entity that paid for the ad, which is often a media agency rather than the brand. Useful for mapping an agency's whole client roster. Up to 20 per run.

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

Two-letter ISO country codes, e.g. DE, FR, US. Applied to every search above. Give countries on their own to sweep everything advertised into that country. Tip: an EU country here also switches on the run dates, impressions and targeting fields, which LinkedIn only discloses for ads delivered into the EU.

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

Limit results to ads served in this window. Leave on "all" for everything the library holds.

## `dateFrom` (type: `string`):

YYYY-MM-DD. Fill both custom dates to use an exact window instead of the preset above. LinkedIn only accepts dates within the last twelve months.

## `dateTo` (type: `string`):

YYYY-MM-DD. Ignored unless the custom start date is filled in too.

## `maxItems` (type: `integer`):

Total rows to return across every search. The budget is shared evenly, so four searches and 200 rows gives you fifty of each. Default 20, hard ceiling 2,000. Keep it low while testing - you pay per ad.

## `includeAdDetails` (type: `boolean`):

On by default, and what you want. It is the only place LinkedIn publishes the untruncated ad copy, the landing URL, the run dates, the impressions band, the country split and the targeting. Switch it off for a fast advertiser/creative-only sweep; the price per ad is the same either way.

## `proxyUrls` (type: `array`):

Leave this empty. By default the run rotates a large pool of addresses that cost you nothing per gigabyte. Fill it in only if you specifically want the traffic to leave through proxy servers you already pay for, in the form http://user:pass@host:port.

## Actor input object example

```json
{
  "companies": [
    "HubSpot"
  ],
  "dateRange": "all",
  "maxItems": 20,
  "includeAdDetails": true
}
```

# Actor output Schema

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

Every row in the default dataset: searchTerm, advertiserName, adType, adText, headline, callToAction, landingUrl, imageUrl, videoUrl, firstShownDate, lastShownDate, totalImpressions, countries, payerName, advertiserUrl, advertiserId, adUrl, adId, creativeType, impressionsByCountry, targeting, runDates, isRestricted, restrictionDetails, searchType. An empty, blocked or unmatched run returns a single uncharged row explaining what happened instead.

# 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"
    ],
    "dateRange": "all",
    "maxItems": 20,
    "includeAdDetails": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/linkedin-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"],
    "dateRange": "all",
    "maxItems": 20,
    "includeAdDetails": True,
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/linkedin-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"
  ],
  "dateRange": "all",
  "maxItems": 20,
  "includeAdDetails": true
}' |
apify call dami_studio/linkedin-ads-scraper --silent --output-dataset

```

## MCP server setup

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