LinkedIn Ad Library Scraper | $1/1K Ads, No Login, HTTP-only avatar

LinkedIn Ad Library Scraper | $1/1K Ads, No Login, HTTP-only

Pricing

from $1.00 / 1,000 ad scrapeds

Go to Apify Store
LinkedIn Ad Library Scraper | $1/1K Ads, No Login, HTTP-only

LinkedIn Ad Library Scraper | $1/1K Ads, No Login, HTTP-only

Scrape the LinkedIn Ad Library without login: ad copy, creatives, payer, run dates, EU impressions and targeting as structured JSON. Search by advertiser, keyword, country or date. Pure HTTP, fast and cheap.

Pricing

from $1.00 / 1,000 ad scrapeds

Rating

5.0

(1)

Developer

Alex Morain

Alex Morain

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

1

Monthly active users

5 days ago

Last modified

Share

LinkedIn Ad Library Scraper

This Actor turns the public LinkedIn Ad Library into structured data. Give it a company slug, a keyword, or a search URL you built on the LinkedIn site, and it returns every ad it finds: advertiser, format, headline, ad copy, creative and video URLs, the paying entity, and for ads served in the European Union the run dates, the impressions range and the targeting the advertiser selected.

It runs over plain HTTP. No browser, no login, no cookies, no LinkedIn account of yours involved. That is why it fits in 512 MB and why a listing run of a few hundred ads finishes in under a minute. Pricing is pay per event: $0.0001 when the run starts, $0.001 per ad returned, $0.002 per ad when you ask for the detail page as well. A first run capped at 100 ads costs about ten cents.

If you were looking for a LinkedIn Ad Library API, this is the closest thing that exists. LinkedIn does not publish one. The library is a web surface built to satisfy the EU Digital Services Act, and this Actor reads that surface the way a browser does, then hands you JSON.


What this LinkedIn ads scraper does

It performs three kinds of search, and they can be combined.

Search by advertiser. You list one or more companies, using the slug from their LinkedIn page URL (linkedin.com/company/nvidia gives you nvidia). Each advertiser becomes its own server-side search, and every item carries the query that produced it so you can split the dataset back apart afterwards.

Search by keyword across the whole library. Used alone, a keyword sweeps every advertiser LinkedIn holds, which is how you find competitors you had not thought to list. Used together with advertisers, it narrows each advertiser's ads down to the ones mentioning that phrase.

Reproduce a search URL. If you built a filter combination on the LinkedIn site that this input does not expose, paste the resulting URL and the Actor replays its parameters exactly.

On top of the search, an optional second pass opens the detail page of each ad. That is where the full ad copy lives, along with the paying entity, the run dates and the whole EU transparency block. It costs one extra request per ad and is billed at the higher rate, so it is off by default.

How to scrape the LinkedIn Ad Library

From the Apify console

  1. Open the Actor and go to the Input tab. One advertiser (nvidia), one country (US) and a cap of 100 ads are already filled in, so pressing Start immediately gives you a real dataset to look at for about ten cents.
  2. Replace the advertiser with the companies you actually want to watch, one per line. Use the slug from their LinkedIn company page URL. If a search returns nothing, the usual reason is that the group advertises under a legal entity rather than its brand name.
  3. Narrow the search if you can. Countries, date range and payer are all applied by LinkedIn itself, server-side, so every filter you set makes the run both faster and cheaper. Leave Countries empty for a worldwide sweep when you do not know where a company advertises.
  4. Keep the Max ads per query cap on your first run. A single large advertiser can be running several hundred ads at once and you pay per ad returned. 100 is enough to see the shape of a competitor's advertising. Set it to 0 once you know what you are getting.
  5. Turn on Include ad details only for the advertisers you are really going to analyse. It is what unlocks impressions and targeting, and it doubles the per-ad price.
  6. Press Start. When the run finishes, open the Dataset tab and export as JSON, CSV, Excel, XML or HTML, or read it straight from the API.

From the API

Every run is also a REST call. The Actor ID is alexmorain/linkedin-ad-library-scraper. The synchronous endpoint below blocks until the run ends and returns the dataset items in the response body, which is the shortest path from a shell to a CSV file.

curl -X POST "https://api.apify.com/v2/acts/alexmorain~linkedin-ad-library-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN&format=csv" \
-H "Content-Type: application/json" \
-d '{
"advertisers": ["nvidia"],
"countries": ["US"],
"maxAdsPerQuery": 100,
"includeDetails": false
}' \
-o linkedin-ads.csv

For anything long-running, start the run asynchronously and collect the dataset later, or attach a webhook so Apify calls you when the run succeeds. Both are covered further down.

Which fields should I fill?

Fill at least one of advertisers, keyword or searchUrls. Everything else is optional and the Actor fails with a readable message if all three are empty, rather than running an empty search and charging you for it.

Input fieldWhat it is forExample
advertisersOne search per company. The main way in. Company names or LinkedIn slugs.["nvidia", "doctolib"]
keywordAlone, searches the entire library for a phrase and surfaces advertisers you did not know about. With advertisers, filters their ads."AI copilot"
searchUrlsReplays a search URL you built by hand on the LinkedIn site, parameters included. The escape hatch for filters this input does not expose.["https://www.linkedin.com/ad-library/search?accountOwner=nvidia&countries=FR"]
countriesISO 3166-1 alpha-2 codes of the countries where the ads were shown. Empty means worldwide. An EU country here is what makes the transparency fields appear.["FR", "DE"]
dateOptionA preset period: all-time, last-30-days, current-month, current-year, last-year. Ignored when the two custom dates are filled."last-30-days"
startDate + endDateAn exact window, YYYY-MM-DD, within the last 12 months. They go together: fill both or neither."2026-05-01" / "2026-05-31"
payerFilters on the "Paid for by" entity, which is often an agency or a legal entity rather than the brand."GRAVITY GLOBAL PERFORMANCE MARKETING LTD"
includeDetailsOpens each ad's detail page. Adds full copy, payer, run dates, impressions and targeting. Doubles the per-ad price.true
maxAdsPerQueryStops each search after N ads. Applies per search, so three advertisers at 100 can return 300 ads. 0 or empty means no limit.100
proxyConfigurationApify datacenter proxy by default, which is enough. Switch the group to RESIDENTIAL if a run gets blocked.{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}

Four combinations cover almost every real job:

If you want toFill inLeave alone
See what a competitor is advertisingadvertisers, a maxAdsPerQuery capeverything else
Discover who advertises on a themekeyword aloneadvertisers
Measure EU reach and read targetingadvertisers, includeDetails: true, an EU countryeverything else
Track one campaign windowstartDate + endDatedateOption

What you get back: every output field

The output shape is stable. Every field listed below is present on every item, set to null or to an empty array when LinkedIn does not publish it, so you can point a schema or a database column at the dataset without defensive code.

Three groups matter, and mixing them up is the single most common source of confusion when people first use a LinkedIn ad transparency dataset.

Fields you get on every ad

These come from the search result cards, so they are filled whether or not includeDetails is on.

FieldTypeDescriptionExample value
ad_idstringLinkedIn Ad Library identifier. Unique, and used internally to deduplicate across searches."1525076296"
detail_urlstringPublic Ad Library URL of the ad. Shareable, and the link a human will want in a report."https://www.linkedin.com/ad-library/detail/1525076296"
advertiserstringThe advertising account name as LinkedIn shows it on the card."NVIDIA"
advertiser_urlstringLinkedIn page of the advertiser. Numeric company ID form when it comes from the detail page."https://www.linkedin.com/company/3608"
formatstringAd format as LinkedIn labels it."Single Image Ad", "Video Ad", "Carousel Ad", "Text Ad"
creative_typestringLinkedIn's internal creative type of the preview. Useful for grouping when format is missing."SPONSORED_STATUS_UPDATE", "SPONSORED_VIDEO"
headlinestringSponsored content headline. null for ads that have no headline, such as promoted personal posts."Securing the AI Era, Together"
body_previewstringThe ad copy as printed on the search card, truncated by LinkedIn at around 120 characters."NVIDIA and industry leaders have launched the Open Secure AI Alliance to develop new techniques and tools to safeguard s…"
image_urlsarray of stringsCreative image URLs on media.licdn.com. Company logos and profile photos are filtered out, so what is left is the creative itself.["https://media.licdn.com/dms/image/v2/D5610AQHII2iv5XLwZg/…"]
querystringThe input query that produced this item, so a multi-advertiser dataset can be split back apart."advertiser:nvidia"
has_detailsbooleanWhether the detail page was actually fetched and parsed for this ad.true

Fields that need Include ad details

Turning includeDetails on opens linkedin.com/ad-library/detail/<ad_id> for each ad. These four fields come back for any ad, wherever it ran.

FieldTypeDescriptionExample value
payerstringThe "Paid for by" entity. Frequently an agency or a legal entity, not the brand on the creative."GRAVITY GLOBAL PERFORMANCE MARKETING LTD"
bodystringFull ad copy, untruncated. Falls back to body_preview when the detail page has no commentary block."At Black Hat, the Open Secure AI Alliance is sharing new open tools for securing AI systems…"
video_urlsarray of stringsVideo sources for video ads, one entry per encoded rendition. Empty for image ads.["https://dms.licdn.com/playlist/vid/v2/…/mp4-720p-30fp-crf28/…"]
promoted_bystringThe company behind a Thought Leader Ad, when the creative is a personal post promoted by a brand."Doctolib"

EU-only fields: the DSA ad transparency block

This is the part worth reading twice. Under the EU Digital Services Act, LinkedIn must publish reach and targeting information for ads that were served in the European Union. For ads that never ran in the EU, that information does not exist anywhere in the library, and the fields below come back null or empty. That is LinkedIn's data boundary, not a gap in the scraper: a US-only campaign will never have impressions, no matter which tool you use.

Both cases are verified in the Actor's test fixtures. A detail page for an EU-served ad fills every field in this table. A detail page for a US-only ad fills payer and body and leaves ran_from, ran_to, all impressions fields, and both targeting fields empty.

FieldTypeDescriptionExample value
ran_fromstringFirst day the ad ran, in LinkedIn's own display format."Apr 23, 2026"
ran_tostringLast day the ad ran. null while the ad is still running."Apr 30, 2026"
impressions_displaystringThe total impressions range exactly as LinkedIn prints it."30k-50k"
impressions_minnumberLower bound, parsed to an integer. null when the range is open-ended below, as in < 1k.30000
impressions_maxnumberUpper bound, parsed to an integer.50000
impressions_by_countryarray of objectsImpression share per country, one {country, share} object each, in LinkedIn's order. Shares are strings because LinkedIn publishes buckets, not percentages.[{"country": "France", "share": "100%"}, {"country": "Belgium", "share": "less than 1%"}]
targeting_segmentsarray of stringsThe readable targeting statements: languages and locations the advertiser targeted.["Targeting includes Français", "Targeting includes France"]
targeting_parametersobjectLinkedIn's targeting table, keyed by category: Audience, Demographic, Company, Education, Job, Member Interests and Traits. Each holds two booleans, targeted and excluded, saying whether the advertiser used that category on each side.{"Job": {"targeted": true, "excluded": true}}

One thing to know about targeting_parameters: it gives you the marks, not the values behind them. LinkedIn publishes that an advertiser used job targeting; it never publishes which job titles. So {"Job": {"targeted": true, "excluded": true}} means the campaign both included and excluded on job criteria, and that is as deep as the public library goes. The readable half of the targeting is targeting_segments, which spells out languages and locations in words. The two work together: the booleans tell you how finely a campaign was built, the segments tell you where and to whom it ran.

Example output

Two items from the same dataset, one without details and one with the full EU transparency block. Both were produced by running this Actor's parsers over recorded LinkedIn Ad Library pages, so the shapes and the values are what you will actually see. The second item's impressions_by_country is cut to six entries here; the real one lists all 37 countries LinkedIn reported for that ad.

[
{
"ad_id": "1525076296",
"detail_url": "https://www.linkedin.com/ad-library/detail/1525076296",
"advertiser": "NVIDIA",
"advertiser_url": null,
"payer": null,
"format": "Single Image Ad",
"creative_type": "SPONSORED_STATUS_UPDATE",
"headline": "Securing the AI Era, Together",
"body": null,
"body_preview": "NVIDIA and industry leaders have launched the Open Secure AI Alliance to develop new techniques and tools to safeguard s…",
"image_urls": [
"https://media.licdn.com/dms/image/v2/D5610AQHII2iv5XLwZg/image-shrink_1280/B56Z.kljQVHUAc-/0/1785172735260/osaia-logo-garden_press-kit_1200x6282png?e=2147483647&v=beta&t=gfETc_HyA-Il1pVCVWH1uBJhtPyFzt9lDWIlNF_3eec"
],
"video_urls": [],
"ran_from": null,
"ran_to": null,
"impressions_display": null,
"impressions_min": null,
"impressions_max": null,
"impressions_by_country": null,
"targeting_segments": null,
"targeting_parameters": null,
"promoted_by": null,
"query": "advertiser:nvidia",
"has_details": false
},
{
"ad_id": "1279725554",
"detail_url": "https://www.linkedin.com/ad-library/detail/1279725554",
"advertiser": "Maric Philibert",
"advertiser_url": "https://www.linkedin.com/company/4999584",
"payer": "DOCTOLIB SAS",
"format": "Video Ad",
"creative_type": "SPONSORED_VIDEO",
"headline": null,
"body": "La meilleure technologie en santé est celle qui rend du temps aux soignants, leur permet de garder le contrôle, et se fait oublier. L'attractivité et la qualité au travail s'en trouvent améliorées. C'est l'intérêt de l'Assistant de consultation de Doctolib. Disponible pour les cabinets, les centres de santé et les hôpitaux.",
"body_preview": "La meilleure technologie en santé est celle qui rend du temps aux soignants, leur permet de garder le contrôle, et se f…",
"image_urls": [],
"video_urls": [
"https://dms.licdn.com/playlist/vid/v2/D4E05AQElNwCRP2JoQg/mp4-640p-30fp-crf28/B4EZ2w64P7J8Bw-/0/1776789729248?e=2147483647&v=beta&t=nYQIDCmIcXIMVJl-qbctC27-CqaaDsR8Aord5ykuQC4",
"https://dms.licdn.com/playlist/vid/v2/D4E05AQElNwCRP2JoQg/mp4-720p-30fp-crf28/B4EZ2w64P7J8CI-/0/1776789733165?e=2147483647&v=beta&t=Ny8CWClU5AH0aAIXjxlOy7OsiPUbSs8XLfcaBBZo3pk"
],
"ran_from": "Apr 23, 2026",
"ran_to": "Apr 30, 2026",
"impressions_display": "30k-50k",
"impressions_min": 30000,
"impressions_max": 50000,
"impressions_by_country": [
{ "country": "France", "share": "100%" },
{ "country": "Switzerland", "share": "less than 1%" },
{ "country": "Belgium", "share": "less than 1%" },
{ "country": "Morocco", "share": "less than 1%" },
{ "country": "Monaco", "share": "less than 1%" },
{ "country": "Algeria", "share": "less than 1%" }
],
"targeting_segments": [
"Targeting includes Français",
"Targeting includes France"
],
"targeting_parameters": {
"Audience": { "targeted": false, "excluded": false },
"Demographic": { "targeted": false, "excluded": false },
"Company": { "targeted": true, "excluded": true },
"Education": { "targeted": false, "excluded": false },
"Job": { "targeted": true, "excluded": true },
"Member Interests and Traits": { "targeted": false, "excluded": false }
},
"promoted_by": "Doctolib",
"query": "advertiser:doctolib",
"has_details": true
}
]

Notice the second item: the advertiser on the card is a person, promoted_by is the company, and payer is the legal entity. Thought Leader Ads split the brand across three fields, and any competitor report that groups only on advertiser will miss them.

Using the LinkedIn Ad Library API from your code

Python

from apify_client import ApifyClient
client = ApifyClient("YOUR_APIFY_TOKEN")
run_input = {
"advertisers": ["nvidia", "doctolib"],
"countries": ["FR"],
"startDate": "2026-05-01",
"endDate": "2026-05-31",
"includeDetails": True,
"maxAdsPerQuery": 100,
"proxyConfiguration": {"useApifyProxy": True},
}
run = client.actor("alexmorain/linkedin-ad-library-scraper").call(run_input=run_input)
for ad in client.dataset(run["defaultDatasetId"]).iterate_items():
reach = ad["impressions_display"] or "not served in the EU"
print(f'{ad["advertiser"]:<20} {ad["format"]:<18} {reach:<12} {ad["detail_url"]}')

Install the client with pip install apify-client. call() blocks until the run finishes; use client.actor(...).start(run_input=run_input) if you would rather poll or rely on a webhook.

JavaScript

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('alexmorain/linkedin-ad-library-scraper').call({
keyword: 'AI copilot',
countries: ['DE'],
dateOption: 'last-30-days',
includeDetails: true,
maxAdsPerQuery: 200,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
// Who is buying the most reach on this message in Germany?
const byAdvertiser = {};
for (const ad of items) {
if (!ad.impressions_max) continue; // non-EU ads carry no reach data
byAdvertiser[ad.advertiser] = (byAdvertiser[ad.advertiser] ?? 0) + ad.impressions_max;
}
console.table(Object.entries(byAdvertiser).sort((a, b) => b[1] - a[1]));

Install with npm install apify-client.

cURL

Start a run and get the dataset back in one call, as JSON this time:

curl -X POST "https://api.apify.com/v2/acts/alexmorain~linkedin-ad-library-scraper/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"advertisers": ["nvidia"],
"countries": ["FR", "DE", "ES"],
"includeDetails": true,
"maxAdsPerQuery": 50
}'

Or start it asynchronously and read the dataset whenever you like:

RUN=$(curl -s -X POST "https://api.apify.com/v2/acts/alexmorain~linkedin-ad-library-scraper/runs?token=YOUR_APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"advertisers":["nvidia"],"maxAdsPerQuery":0}' | jq -r '.data.id')
curl -s "https://api.apify.com/v2/actor-runs/$RUN/dataset/items?token=YOUR_APIFY_TOKEN&format=csv" -o ads.csv

What people use it for

Competitor ad analysis on LinkedIn

The obvious one, and the reason most people arrive here. Put your five closest competitors in advertisers, leave the details off, cap at 100, and schedule the run weekly. You get their whole active creative set with format, headline and copy preview, which tells you what they are selling this quarter, which formats they trust, and how the message drifts month to month. Because ad_id is stable across runs, diffing two datasets gives you the new ads and the retired ones without any extra tooling. This is B2B ad intelligence you can run for a few cents a week, and unlike a paid ad-spy product the data comes from LinkedIn itself rather than from a panel.

Budget and campaign intensity from LinkedIn ads impressions data

For anything served in the EU, impressions_min and impressions_max give you a reach bracket per ad, and ran_from/ran_to give you the flight. Sum the midpoints across a competitor's ads for a quarter and you have a defensible order of magnitude for how hard they are pushing, without pretending to know their CPM. The interesting signal is usually not the absolute number but the shape: an advertiser whose per-ad impression brackets jump a tier in March and stay there has changed budget, and impressions_by_country tells you which market absorbed it. This is the closest thing to competitor spend data that exists in public, and it is the direct consequence of EU DSA ad transparency rules.

Creative research before you brief

Run a keyword sweep across the whole library on the concept you are about to advertise on, with details on, and read the body field of the fifty ads that came back. You are looking at copy that companies were willing to pay to distribute, which is a better corpus than a brainstorm. image_urls and video_urls point at the actual creatives on LinkedIn's CDN, so a designer can pull the reference set in one command. Sorting the same result by impressions_max puts the ads that got real budget behind them at the top, which is a crude but useful proxy for what the advertiser thought was working.

Detecting a competitor entering a market

Set countries to the market you care about, list the companies you watch, and schedule a run. The first day a competitor's ads show up in that country, you know they are entering it, usually weeks before a press release. impressions_by_country sharpens this further: an advertiser can serve ads in France with 100% of impressions in France, or serve the same ad across eight countries with a long tail under 1%, and those are two very different intentions. Combine it with payer and you can also catch the case where a group enters a market through a local subsidiary that advertises under its own legal entity.

Reading targeting choices

targeting_segments tells you the languages and locations an advertiser selected for an EU-served ad, in LinkedIn's own words. Across a competitor's whole corpus, that is a map of who they think their buyer is: which countries get dedicated creatives, which get a pan-European catch-all, and which languages they invest in localising for. When the same message runs in three languages with separate creatives, someone decided that market was worth the production cost. That is a stronger signal about their roadmap than most things they will say publicly.

targeting_parameters answers a different question: how carefully was this campaign built. Six categories, two booleans each, one pair for every row LinkedIn lists. An ad with Company and Job both targeted and excluded came from someone who drew a real audience and then carved segments back out of it. An ad with every category false is a broad geographic push. Count the true values across a competitor's corpus and you get a readable measure of how deliberate their paid programme is, campaign by campaign.

Building a lead or partner list from advertisers

A keyword sweep with no advertiser filter returns every company currently paying to talk about a topic. Companies advertising on a theme are, by definition, spending money on it, which makes them qualified for anything you sell into that theme. advertiser_url gives you the LinkedIn page to enrich from, and payer frequently surfaces the agency behind the account, which is a second list of its own if you sell to agencies.

Why this LinkedIn ad library scraper

The custom date range trap

This is the difference that costs other people their data. LinkedIn's Ad Library silently ignores startdate and enddate in a search URL unless dateOption=custom-date-range is sent alongside them. A request carrying only the two dates comes back with the full corpus, HTTP 200, no warning, no error. A scraper that passes the dates through and trusts the response will hand you an unfiltered dataset while its logs claim the filter was applied.

The measurement, on an advertiser with 349 ads: asking for May 2026 with the complete parameter set returns 21 ads. Asking for the same window without the third parameter returns all 349. If your date filtering ever looked suspiciously generous, that is why.

This Actor always sends the three parameters together, and it refuses an input with only one of the two dates rather than quietly doing something you did not ask for. The same applies to a URL you paste into searchUrls: if you build a dated search by hand on the LinkedIn site, copy the whole URL, dateOption included.

Complete pagination

The last page of Ad Library results omits its pagination metadata block entirely. Parsers that expect the block on every page either crash on the last one or stop before it. This one treats a missing block as the end of the corpus, which is what it is, and walks the pagination fragments to the end. On the reference measurement that means 347 unique ads collected out of 349 announced.

Deduplication happens across the whole run, not per search, keyed on ad_id. An ad that appears in two of your searches is pushed once and billed once.

Plain HTTP, no browser, no login

There is no headless browser here and no LinkedIn session. The Actor issues HTTP requests to public URLs and parses the returned HTML, which has three consequences you can feel. It runs in 512 MB instead of the several gigabytes a browser-based scraper needs, so the platform usage underneath the per-ad price is small. A listing run of 347 ads took 51 seconds. And there is no account of yours to get restricted, because none is used.

The trade-off is deliberate pacing: requests go out one at a time, at least 2.5 seconds apart, because the Ad Library rate-limits an IP after roughly nine rapid requests. Listing runs are fast because a page of results carries 24 ads. Detail runs are slower, since each ad needs its own request, which is another reason to sweep first and enrich second.

Resistance to the bot check

LinkedIn put a bot check in front of the Ad Library at the start of September 2026, and it filters on the TLS and HTTP/2 fingerprint of the client rather than on the IP address or the headers. A plain HTTP library gets a 403 regardless of how good its proxy is or how carefully its headers are set.

The Actor's HTTP client replays the fingerprint of a real browser and rotates through five browser profiles. On a block it changes both the profile and the proxy exit IP, backs off, and retries, so a transient challenge costs you a few seconds rather than the run. If a large run still gets blocked, the fix is to set the proxy group to RESIDENTIAL in the input and run it again; the failure message says exactly that, so you do not have to guess.

Failure that tells you what to do

A search that fails degrades on its own: the other searches keep going and you get what was collectable. A run where every search failed ends as FAILED, never as a green run with an empty dataset, and the status message distinguishes the two cases that need opposite reactions. Blocked by the bot check tells you to switch to residential proxies. Zero results everywhere tells you to check the advertiser spelling, because the ads may simply not exist.

Ads are billed as they are pushed, so a run that stops early bills only what it delivered, and it stops cleanly when it hits the maximum charge you set on the run.

Pricing

Pay per event, in US dollars, with no monthly fee and no platform usage on top: the price below is the price.

EventPriceWhen it fires
Actor start$0.0001Once when the run starts
Ad scraped$0.001Per ad pushed from the search results, that is $1 per 1,000 ads
Ad scraped with details$0.002Per ad whose detail page was fetched and parsed, that is $2 per 1,000 ads

An ad is billed once, at one rate or the other, never both. If includeDetails is on but a particular detail page cannot be fetched, that ad is pushed with the fields it has and billed at the cheaper listing rate.

Three worked examples, using the reference advertiser whose US corpus is 344 ads:

  • Sweep all 344 ads without details: 344 × $0.001 + $0.0001 = $0.34.
  • Same 344 ads with the full EU transparency block: 344 × $0.002 + $0.0001 = $0.69.
  • Weekly monitoring of five competitors, capped at 100 ads each, no details: 500 × $0.001 + $0.0001 = $0.50 per run, so about $2 a month for a standing watch on five companies.

The cheapest useful pattern is to sweep wide with details off, decide which advertisers are worth the depth, then re-run those few with details on. Set a maximum cost per run in the run options if you want a hard ceiling regardless of what the library returns.

Limits and what this scraper does not do

The Ad Library holds roughly the last 12 months of ads. Whatever you put in startDate, nothing older comes back, and all-time in the dropdown means the last twelve months. If you need a longer history, the only way is to start collecting now and keep your own archive, which a scheduled run and a growing dataset do for you.

Impressions, run dates and targeting exist only for ads served in the European Union. This is the DSA transparency boundary and it applies to every tool reading this library. A US-only campaign has no reach data anywhere in the public Ad Library.

LinkedIn's announced total is higher than what it actually serves through pagination. On the reference search, LinkedIn announced 349 ads and the pagination walked out at 347 unique ones. The gap is small and comes from LinkedIn's own counter, not from a parsing failure, so do not be alarmed if a run returns a handful fewer ads than the number on the LinkedIn page.

The impressions filter that exists on the LinkedIn site is deliberately not exposed here. Combined with an advertiser and a country filter it inflates the announced total rather than narrowing it, in a way that is not yet understood, and shipping a filter that returns more results than no filter at all would be worse than not shipping it.

targeting_parameters tells you which categories a campaign used, never the values inside them. LinkedIn publishes that an advertiser targeted on job criteria and not which job titles, so no tool reading this library can give you the audience definition itself. The categories and their targeted and excluded marks are what the library exposes, and this Actor reads all of them.

Finally, the scope. This Actor reads the public Ad Library and nothing else. It does not touch member profiles, connections, feeds, messages, company pages, job posts or anything behind a login, and it will not click through to an advertiser's landing page for you.

FAQ

Can I scrape LinkedIn ads without logging in?

Yes, and that is how this Actor works. The Ad Library is public, so no LinkedIn account, cookie or session token is involved anywhere in a run. You never hand over credentials, and there is no account of yours exposed to a restriction.

Is there a LinkedIn Ad Library API?

LinkedIn does not publish one. The library is a web surface, built to meet EU transparency obligations, with no documented public API behind it. This Actor is the practical substitute: you call the Apify API, it reads the library, and you get JSON or CSV back with a stable schema.

How far back does the LinkedIn Ad Library go?

About 12 months, rolling. The date picker on LinkedIn's own site does not offer anything older, and the search returns nothing beyond it. Ads that stopped running more than a year ago are gone from the public library.

Which ads have impressions data?

Only ads that were served in the European Union. The Digital Services Act requires LinkedIn to publish reach and targeting for those, and LinkedIn publishes nothing equivalent for ads served elsewhere. In practice: if impressions_display is null, the ad did not run in the EU. Setting an EU country in countries is the reliable way to build a dataset where these fields are populated.

Why are my impressions and targeting fields empty?

Two possible reasons, in this order. Either includeDetails was off, in which case none of the detail-page fields are filled for any ad. Or the ads were not served in the EU, in which case payer and body are filled but the reach and targeting fields are not. has_details tells you which of the two you are looking at.

How do I export LinkedIn ads to CSV or Excel?

Every run writes to an Apify dataset, and the dataset exports as CSV, JSON, XML, Excel, RSS or HTML from the console in one click. From the API, add &format=csv or &format=xlsx to the dataset items endpoint. Nested fields such as impressions_by_country are flattened into columns on export.

The Ad Library is LinkedIn's public ad transparency tool, published in large part to satisfy the EU Digital Services Act, and this Actor reads only that public library. It extracts no private user data: no email addresses, no member profiles, nothing behind a login. Your results can still contain personal data, though, since the person behind a Thought Leader Ad appears by name. Personal data is protected by the GDPR and by comparable rules elsewhere. Scrape it only if you have a legitimate reason, and if you are unsure whether yours qualifies, ask your lawyers rather than the internet.

An advertiser returns zero ads. What now?

Check the slug first: it is the last segment of the company's LinkedIn page URL, not their trading name. Then remember the 12-month window, and that many large groups advertise under a legal entity rather than a brand. Searching the library by keyword for the brand name and reading the advertiser field of the results is the quickest way to find the account name LinkedIn actually uses.

How much does it cost to scrape 1,000 LinkedIn ads?

$1 without detail pages, $2 with them, plus $0.0001 for the run itself. Nothing else is charged on top.

My run was blocked. What do I change?

Set the proxy group to RESIDENTIAL in the input and run it again. LinkedIn puts a bot check in front of the library and datacenter IP ranges are the ones it challenges first. The Actor already rotates browser fingerprints and proxy sessions on its own, so a residential exit IP is the remaining lever, and the failure message tells you so when it happens.

Can I get ads from a specific date range?

Yes. Fill startDate and endDate together, in YYYY-MM-DD, within the last 12 months, and the Actor sends the complete parameter set LinkedIn needs for the range to actually apply. Do not fill only one of the two: the run fails with a message rather than silently returning the whole corpus.

Can I search all advertisers at once instead of naming them?

Yes, with keyword alone and no advertisers. That runs one search across the entire library and returns every advertiser using the phrase. Cap it with maxAdsPerQuery on the first try, since a broad term can match a lot of ads and you pay per ad returned.

How long does a run take?

Listing runs are quick: a page of search results carries 24 ads, so a few hundred ads take under a minute. Runs with includeDetails on are much slower, because each ad needs its own request and requests are paced at least 2.5 seconds apart to stay under LinkedIn's rate limit. Budget roughly one ad every three seconds in detail mode and plan the timeout accordingly.

Integrations, exports and agents

Runs can be scheduled from the Apify console, hourly through monthly, which is all a standing competitive watch needs. Attach a webhook on the ACTOR.RUN.SUCCEEDED event and Apify will POST the run and dataset IDs to your endpoint, so a monitoring pipeline can pull the new ads into your own store without polling.

The dataset is readable over the Apify API in JSON, CSV, XML, Excel, RSS and HTML, and Apify's integrations push it to Google Sheets, Slack, Airtable, Zapier, Make and the usual storage targets without any code from you.

The Actor is also callable from an agent. Anything that speaks the Apify API can start a run and read the dataset, and the Apify MCP server exposes Actors as tools to MCP-capable clients, which makes "find every ad this company is running in Germany" a single tool call inside an assistant.

Support

Found a bug, a field that parses wrong, or a filter you need? Open a ticket in the Issues tab of the Actor. It is read, and a reproducible input gets a fix faster than a description. For programmatic access, the API tab of the Actor page has the endpoints and client snippets prefilled with your token.