LinkedIn Ad Tracker — Competitor Ads, Payer & Targeting avatar

LinkedIn Ad Tracker — Competitor Ads, Payer & Targeting

Pricing

from $4.00 / 1,000 results

Go to Apify Store
LinkedIn Ad Tracker — Competitor Ads, Payer & Targeting

LinkedIn Ad Tracker — Competitor Ads, Payer & Targeting

Name a competitor and get the ads it runs in LinkedIn's public Ad Library: creative copy, payer, format, run dates, EU impression ranges and targeting, plus the advertiser's firmographics (domain, industry, size). Goes past the ~24-ad limit. No login. Says so when LinkedIn refuses a run.

Pricing

from $4.00 / 1,000 results

Rating

0.0

(0)

Developer

Berkan Kaplan

Berkan Kaplan

Maintained by Community

Actor stats

0

Bookmarked

3

Total users

2

Monthly active users

10 hours ago

Last modified

Share

LinkedIn Ad Tracker 📣

foXLabs LinkedIn series: Company profiles · Company 360 · Jobs · Hiring signals · Ad discovery

Name a competitor and get the ads it runs in LinkedIn's public Ad Library — one row per ad, with the payer, creative copy, format, run dates and (for EU-served ads) impression range and targeting, plus the advertiser's firmographics. No login.

✅ No proxy setup needed. LinkedIn's Ad Library refuses an exit IP after it has fetched a number of ad pages. The Actor loads the pages in a real browser and rotates across fresh Apify datacenter exits (the default) until every ad is collected — so a run delivers an advertiser's full list at no proxy cost. You can switch to a residential/mobile or custom proxy if you prefer. If LinkedIn refuses a run run-wide, it says so (status message + SOURCE_REPORT) and charges only the ads it delivered, never an empty "success".

🔍 What is the LinkedIn Ad Tracker — and when should you use it?

Give this Actor advertisers — company names, LinkedIn company URLs or numeric company IDs — and it reads their ads from the public LinkedIn Ad Library, live, and returns them as deduplicated rows you can filter, export or hand to an AI agent.

Use it when you need: a competitor's current LinkedIn creatives and messaging; who pays for the ads (agency or brand); which formats they run; and, for ads served in the EU, the impression range and targeting LinkedIn publishes under the Digital Services Act.

Use something else when: you need spend — the Ad Library publishes impression ranges, never spend; you need engagement (likes, clicks); or you need a guaranteed complete archive on a schedule — see the status note above.

🤖 Use with AI agents

Already on the Apify MCP server? Ask for this Actor by name: foxlabs/linkedin-ad-tracker.

Your agent can pay for its own runs. This Actor is pay-per-event with agentic payments, so an agent can discover it, run it and settle the bill over x402 (USDC on Base) or Skyfire — no Apify account or API token of its own. Billing is the same either way: per delivered ad.

Otherwise paste this into Claude, ChatGPT, Cursor or any MCP-enabled assistant:

I want to pull a competitor's LinkedIn ads using the Apify Actor `foxlabs/linkedin-ad-tracker`.
Input: `advertisers`, `dateRange`, `maxAdsPerAdvertiser`, `maxResults` and more — see the Input table below. `maxResults` caps how many ads are returned.
Start with: {"advertisers":["Salesforce","HubSpot"],"maxResults":500}
Ask me which companies to look up, run the Actor, read the run's status message (it says when a run is partial), then summarise the rows as a table.

The machine-readable API, MCP config and OpenAPI definition live at apify.com/foxlabs/linkedin-ad-tracker.md.

📋 Overview

Why teams pick this Actor:

  • ✅ One advertiser in, its ads out — by name, LinkedIn company URL or numeric company ID.
  • 🧩 More than LinkedIn's ~24 ads per query — the Actor drives LinkedIn's own infinite scroller in a real browser, up to maxAdsPerAdvertiser.
  • 🇪🇺 EU transparency first — it collects EU-served ads first, because those are the ones LinkedIn attaches impression range, targeting and precise run dates to.
  • 🏢 Advertiser resolved to a company — domain, industry, employee count, HQ and followers from the advertiser's LinkedIn page, attached to its ad rows.
  • 🔑 No proxy setup — the Actor rotates across free Apify datacenter exits until every ad is collected; bring your own residential/mobile proxy only if you prefer.
  • 🧾 Honest runs — a refusal is reported as a refusal, never as "0 ads"; a partial run is labelled PARTIAL with the reason.

✨ Features

  • 🔍 Three ways to name an advertiser — company name, LinkedIn company URL (https://www.linkedin.com/company/hubspot/) or numeric company ID. A name is resolved to the company page, and the page is accepted only when its own name matches what you typed.
  • 🗓️ Date windows that mean what they say — dateRange uses the filters LinkedIn actually honours; rolling windows (7 / 30 / 90 days) are narrowed on our side too, and each row says in dateRangeVerified whether its dates could be checked.
  • 🇪🇺 EU transparency fields — impression range, targeting language and targeting location, on the ads LinkedIn publishes them for (ads served in the EU).
  • ⏱️ Time budget — maxRunSeconds stops the run and keeps what it has instead of losing it to a timeout.
  • 🧬 Ownership — resolves the advertiser to its ultimate owner where UK holding chains make that possible; omitted when not sure. On by default, switch it off with includeOwnership.

🎬 Quick Start

curl -X POST "https://api.apify.com/v2/acts/foxlabs~linkedin-ad-tracker/runs?token=YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"advertisers":["Salesforce","HubSpot"],"maxResults":500}'

🚀 Getting Started (3 steps)

  1. Choose your advertisers — names, LinkedIn company URLs or numeric company IDs.
  2. Set the caps — maxAdsPerAdvertiser per company, maxResults across the run, maxRunSeconds for time.
  3. Run, then read the status message — it tells you whether the run is complete or partial. Export the dataset as JSON, CSV or Excel.

📥 Input

{"advertisers":["Salesforce","HubSpot"],"maxResults":500}
FieldTypeDescription
advertisersarrayCompanies whose ads you want to track — a name, a LinkedIn company URL, or a numeric company id. E.g. "Salesforce", "https://www.linkedin.com/company/hubspot/".
dateRangestringRestrict to ads shown within this window, or All for the full ~1-year retention. LinkedIn's Ad Library natively offers Last 30 days / This month / This year /…
maxAdsPerAdvertiserintegerCap on ads collected per advertiser (1–1000). The Actor drives LinkedIn's infinite scroller in a real browser to go past the ~24-ads-per-query limit and reach this.
maxResultsintegerMaximum number of ad records to push across all advertisers combined — helps control cost. Set 0 for unlimited.
maxRunSecondsintegerStop the run after this many seconds and keep whatever has been collected, instead of being cut off by the run timeout and losing it. 0 = no budget — the run…
enrichAdvertiserbooleanResolve each advertiser to its real company and attach firmographics (domain, industry, employee count, HQ, followers). On by default — this is what no other…
includeOwnershipbooleanResolve each advertiser to its ULTIMATE beneficial owner — a real person or a cross-border parent company — by walking UK holding-company ownership chains…
proxyConfigurationobjectDefaults to Apify datacenter proxy — no setup needed. LinkedIn refuses an exit after it has fetched a number of ad pages, so the run rotates across fresh datacenter exits until every ad is collected. Switch to residential/mobile or your own proxy if you prefer; the selected proxy is also used for advertiser-website/ownership lookups.

📤 Output

One row per ad, saved to the dataset. Only ads are written there: a run never adds error or summary rows.

FieldDescription
adId, adUrlLinkedIn's ad id and the ad's public Ad Library page
advertiserNameThe advertiser, as named on its LinkedIn company page (or as you typed it)
advertiserCompanyId, advertiserUrlLinkedIn's numeric company id and company link shown on the ad
paidByWho paid for the ad — often an agency, not the advertiser
headline, bodyCreative copy. body is the ad text; headline exists only on formats that have one (e.g. sponsored content, carousel)
formatimage, video, carousel, document or message; null when it can't be determined
imageUrl, advertiserLogoUrlThe creative image and the advertiser's logo. imageUrl is best-effort — LinkedIn lazy-loads creatives, so it is sometimes null
cta, clickUrlCall-to-action button label and the advertiser's landing URL — present when the ad has them (many ad types carry neither); null when absent
availabilityStart, availabilityEnd, adDurationDaysRun dates, on the ads LinkedIn publishes them for
impressionsImpression range — part of the transparency details LinkedIn publishes for ads served in the EU
impressionsPerCountryArray of {country, share} — the per-country impression split (EU-served ads), e.g. Germany 100%, Ireland 25%
targetingLanguage, targetingLocationTargeting parameters — same transparency details, EU-served ads
dateRangeVerifiedPresent when you asked for a rolling window: whether this ad's dates could be checked against it
advertiserCompanyFirmographics object: domain, website, industry, employeeCount, hq, followers, and ownership when resolved
sources, scrapedAtWhere each part came from, and when it was read

How full are the fields? For EU-served ads — which the Actor collects first — paidBy, body, format, impressions, targetingLanguage, targetingLocation, run dates and advertiserCompany fill on nearly every ad; headline only on formats that have one; imageUrl is best-effort (lazy-loaded). For ads served outside the EU, LinkedIn publishes no DSA transparency, so those rows carry copy + payer + firmographics but no impressions/targeting/dates. cta/clickUrl fill on the ads that have a call-to-action and an external landing (many formats carry neither); impressionsPerCountry fills on EU-served ads.

Two more things every run writes:

  • Status message — "N ad(s) delivered", or "N ad(s) delivered — PARTIAL: …" with the reason (LinkedIn refusing ad pages, the time budget, an advertiser whose search was refused), or "No ads found for …". A run that collected nothing because LinkedIn refused it ends FAILED with that explanation.
  • SOURCE_REPORT (key-value store) — per advertiser: searches served and refused, ads LinkedIn listed, ads requested, ads delivered, ad pages refused; plus the HTTP response counts of the run.

💼 Use cases

1. Competitor ad tracking — see a competitor's live ads. Input: advertiser names. Output: ads + creatives + dates. Use: a tracking dashboard.

2. Creative research — study the formats and messaging of the leaders in a category. Input: advertiser names. Output: creatives + formats. Use: a swipe file.

3. Agency mapping — find out which agencies run which brands. Input: advertiser names. Output: paidBy per ad. Use: a prospect list for agency sales.

🔗 Integration

JavaScript / Node.js

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_TOKEN' });
const run = await client.actor('foxlabs/linkedin-ad-tracker').call({"advertisers":["Salesforce","HubSpot"],"maxResults":500});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(run.statusMessage, items[0]);

Python

from apify_client import ApifyClient
client = ApifyClient('YOUR_TOKEN')
run = client.actor('foxlabs/linkedin-ad-tracker').call(run_input={"advertisers":["Salesforce","HubSpot"],"maxResults":500})
for item in client.dataset(run['defaultDatasetId']).iterate_items():
print(item)

Automation (n8n / Zapier / Make): schedule or webhook → HTTP request to the Actor API with your input → check the run status and status message → push the dataset to a sheet, CRM or dashboard.

📊 Pricing

Pay-per-event: $0.004 per delivered ad ($4 per 1,000 ads) plus the Actor-start event ($0.00005 per GB of memory). A run that delivers no ads — because the advertiser has none or because LinkedIn refused the run — costs only the start event. View current pricing.

❓ FAQ

Do I need an account, login or API key? No. This reads the public LinkedIn Ad Library.

What do I search by? Company names, LinkedIn company URLs or numeric LinkedIn company IDs.

Why did my run return fewer ads than LinkedIn lists? LinkedIn refuses an exit IP after it has fetched a number of ad pages. The run rotates across fresh datacenter exits to collect the rest, but on a hard day the pool's exit budget can be reached before every page is served — the run then reports a PARTIAL result and a later run usually reaches the rest. The status message and SOURCE_REPORT say how many ads LinkedIn listed, how many arrived and how many pages were refused. Switching to a residential/mobile proxy can help for stubborn advertisers.

Why is a large advertiser slow? The Actor loads pages in a real browser and scrolls to collect every ad, which is slower than a plain HTTP request. Set maxRunSeconds to cap the wait; the run keeps what it has and says it was partial.

Which proxy should I use? None needed — the default Apify datacenter proxy works, because the run rotates across fresh datacenter exits as LinkedIn refuses them. A residential or mobile proxy can help for stubborn advertisers; for the EU transparency fields, an EU-country exit helps.

Is ad spend included? No. LinkedIn publishes impression ranges for EU-served ads, not spend.

How current is the data? Every run reads the Ad Library live.

Can I export to CSV / Excel / JSON? Yes — directly from the Apify dataset.

🐛 Troubleshooting

  • Run FAILED with "LinkedIn refused the Ad Library requests" — nothing is wrong with your input, and no ads were charged. The datacenter pool was refusing run-wide; run again (exits rotate), or switch to a residential/mobile proxy in the Proxy field.
  • Status says PARTIAL — read the reason: refusals (run again later), the time budget (raise maxRunSeconds, or set it to 0), or an advertiser that was not started.
  • "No ads found" for a company you know advertises — pass its LinkedIn company URL instead of the name, so the Actor does not have to guess the page.
  • Fewer rows than maxAdsPerAdvertiser — the advertiser has fewer ads in the window, or maxResults capped the run.

This actor reads the publicly available LinkedIn Ad Library. Results can still contain personal data (e.g. a person’s name); personal data is protected by the GDPR and similar laws, so only process it with a legitimate basis. See Apify’s blog post on the legality of web scraping.

Not affiliated with, endorsed by or sponsored by LinkedIn Corporation.

🤝 Support & contact

Changelog

0.1.43 — 2026-10-12 — Default to free Apify datacenter proxy (no setup, no proxy cost)

  • Datacenter exits serve the Ad Library just as well as residential once the run rotates across them, and datacenter transfer is free — so the default proxy is now Apify datacenter and no proxy setup is needed. Residential/mobile and custom proxies remain available. Input, README and FAQ updated accordingly.
  • No pricing change.

0.1.42 — 2026-10-12 — Block images/media/fonts/CSS to cut residential-proxy data (lower cost)

  • The browser now aborts images, media, fonts and stylesheets — the Actor parses the HTML/DOM only, so downloading those bytes was waste. This cuts residential-proxy data transfer (the dominant cost of a run) with no change to the data returned.
  • No pricing change. No output change.

0.1.41 — 2026-10-12 — Mid-run exit rotation: refused ad pages are re-fetched on a fresh exit (far higher delivery)

  • A residential exit IP serves only a limited number of ad detail pages before Cloudflare starts refusing (a per-exit request budget). The run now rotates to a fresh exit IP when the current one starts refusing and re-fetches the refused ad pages there, until the ads are collected or the exit budget is reached — so advertisers deliver far more of their ads and low-ad advertisers stop coming back empty.
  • Bounded cost: at most AD_MAX_DETAIL_EXITS distinct exits fetch detail pages per run (default 3, range 1–6). AD_DETAIL_CONCURRENCY (default 3, range 1–5) tunes how many ad pages are fetched at once.
  • No pricing change. No output change; only delivered ads are charged.

0.1.29 — 2026-10-11 — Automatic exit-IP rotation (works on shared proxy pools, not just a clean BYOP)

  • The run now rotates the exit IP to find one LinkedIn serves (up to 6), probing a stable search per exit, instead of committing to a single exit and failing if it was refused. Caches the served exit in a named store and tries it first next run.
  • No pricing change. No output change.

0.1.28 — 2026-10-11 — CTA, landing URL and per-country impressions now delivered

  • impressionsPerCountry (array of {country, share}) is now parsed from LinkedIn's per-country bars.
  • cta (button label) and clickUrl (the advertiser's landing URL, ?trk= stripped) are extracted where the ad has them; ads without a CTA keep them null.
  • No extra requests (the page is already scrolled in 0.1.27). No pricing change.

0.1.27 — 2026-10-11 — pages now load in a real browser through a clean proxy you supply (BYOP); refreshed parser; EU-first collection

  • Access rebuilt around a clean exit IP. The Ad Library sits behind a Cloudflare rule that blocks by exit IP, not by request shape, with no solvable challenge on a clean exit. The Actor now loads LinkedIn pages in a real stealth browser through the proxy you supply — supply a clean residential/mobile proxy (Custom proxies). Apify's shared pools are usually refused on this path.
  • Past the ~24-ad cap by scrolling. The browser drives LinkedIn's infinite scroller (24 on a plain load → 120 after scrolling), replacing the old date×country fan-out.
  • EU-served ads collected first. LinkedIn publishes the DSA fields (impression range, per-country, targeting, precise dates) only for EU-served ads; collection surfaces those first, then backfills the rest (copy + payer + firmographics).
  • Refreshed detail parser against the 2026-10-11 DOM: body, headline, format, run dates, impressions, targeting re-derived across single-image, sponsored-message and carousel ads. imageUrl best-effort (lazy-loaded); cta/clickUrl and per-country not reliably delivered yet (left null, never guessed).
  • Honest runs unchanged; a refusal still ends FAILED with the reason and charges no ads. No pricing change.

0.1.26 — 2026-10-06 — connections LinkedIn serves are kept for the run; a refused run now says so

  • Connections that work are re-used. LinkedIn serves its Ad Library to some proxy exits and refuses others, and it keeps serving an exit it has served once: in our tests of 2026-10-06 a served connection carried up to 120 requests in a row, and a refused one stayed refused. The Actor used to open a new connection for every try, which re-rolled a small chance each time — the prefilled example (up to 300 ads) delivered 27 ads that day. It now looks for connections LinkedIn serves, keeps them for the run and sends one request at a time through each; a request is tried up to eight times (was four).
  • Which proxy. With Apify Proxy, LinkedIn pages now go through Apify datacenter sessions (served far more often than residential exits in the same tests). The proxy group you select is used for the advertisers' own websites and as the second choice for LinkedIn. With your own proxy URLs nothing changes.
  • A run that LinkedIn refused no longer ends as a quiet "0 ads" success. LinkedIn refuses many automated Ad Library requests at present. Until now a run whose advertiser searches were all refused finished SUCCEEDED with an empty dataset, and the explanation was only in the log. Such a run now ends FAILED with a status message that says LinkedIn refused it and that no ads were charged.
  • Partial runs are labelled. The status message now reads "N ad(s) delivered — PARTIAL: …" with the reason (LinkedIn refusing ad pages, the time budget, an advertiser whose search was refused or that was not started). "No ads found for …" is reserved for advertisers LinkedIn answered for and listed no ads.
  • New SOURCE_REPORT record in the key-value store: per advertiser, the searches served and refused, the ads LinkedIn listed, requested and delivered, and the ad pages refused; plus how many connections were tried and served.
  • The run stops when LinkedIn serves too few ad pages (fewer than a quarter of at least 30), keeping what it has instead of spending the time budget on refusals.
  • The time budget now also covers the advertiser search, which could overrun it by minutes.
  • Prefilled example: 60 ads per advertiser (was 150). The default when the field is left out is unchanged (150).
  • README rewritten against the Actor's real behaviour. It still carried company-registry wording ("registry ID lookup", "formation date", "legal form") that never applied to an ad tracker, promised a per-country impression split that is not delivered, and listed half of the output fields. The pricing sentence now names the events.
  • No output field was renamed or removed. No pricing change.

0.1.25 — 2026-09-20 — README examples corrected against the real input schema

  • The README's code examples did not match this Actor. They used queries and maxResultsPerQuery — keys that do not exist in this Actor's input schema — with a placeholder value, and the input table listed those same phantom fields. Anyone who copied the AI-agent, cURL, JavaScript or Python example got a failing run. Every example now uses the real schema and matches the Console prefill: {"advertisers":["Salesforce","HubSpot"],"maxResults":500}
  • The input table is regenerated from input_schema.json, so it lists the fields the Actor actually accepts.
  • Removed claims carried over from the same generator template where present: "formation / status monitoring", "a canonical registry record for KYB and due diligence", "every row carries query", and industry described as a NACE code.
  • No code, output field or pricing change.

0.1 — 2026-09-07

  • Enabled AI-agent payments (x402) + rebuilt the README to the full standard (What-is / when, AI-agents + x402 agentic payments + MCP, Overview, Features, Use cases, Integration, FAQ, Troubleshooting, Support & contact).

0.0

  • Initial release: data from the public LinkedIn Ad Library by name or registry ID.