LinkedIn Ads Transcript Scraper — Ad Library Transcription avatar

LinkedIn Ads Transcript Scraper — Ad Library Transcription

Pricing

from $8.00 / 1,000 ad creative transcripts

Go to Apify Store
LinkedIn Ads Transcript Scraper — Ad Library Transcription

LinkedIn Ads Transcript Scraper — Ad Library Transcription

LinkedIn Ad Library ads as text: video ads become transcripts with the first-3s hook, image ads return their on-image copy, plus CTA and advertiser data per ad. Search by advertiser, keyword or market — no cookies, no login. No start fee; text-free, silent and blocked ads are never charged.

Pricing

from $8.00 / 1,000 ad creative transcripts

Rating

0.0

(0)

Developer

Steadyfetch Team

Steadyfetch Team

Maintained by Community

Actor stats

0

Bookmarked

1

Total users

0

Monthly active users

16 hours ago

Last modified

Share

Give it an advertiser name or a keyword and get their LinkedIn Ad Library ads back as text. Every video ad comes back with its full transcript and the first-3-seconds hook; every image ad with its on-image copy (headline, body, CTA); both with the advertiser, headline, ad text, CTA and paying entity, one JSON row per ad. From $8.00 per 1,000 ad creative transcripts on the Business plan ($20.00 on the free plan), platform usage included, no start fee. You are charged only when text lands in your dataset: creatives with nothing readable, blocked pages and failures cost $0. No cookies, no LinkedIn login.

Just want to see it work? Click Start with nothing set and the run is a 3-creative sample — a browse of the United States, last 30 days, read and charged like any run (about $0.06 on the free plan, $0.024 on Business). Changed only an option — the image-text or on-screen-text toggle, a cap, New ads only, a watchlist name — and named nothing? Those settings are applied to the same sample and the run goes ahead, charged like any run, with one uncharged sample_note row saying which settings were yours; a cap smaller than the sample is honoured exactly, a bigger one never grows it. You can also browse a market's recent ads with no keyword at all (country + date range), paste an Ad Library search link, or chain it straight after any LinkedIn Ad Library scraper run. Running the same ads again? An ad already delivered to your account is handed back from that run and not charged a second time — automatically, whatever mode found it, on any run that names what to read (the no-input sample always reads current ads). Watching competitors over time? Name a watchlist and re-run on a schedule: with New ads only you get only what is new (see Watch competitors on a schedule).

New: image ads are read too, and that changes your bill. About 4 in 5 LinkedIn ads are static image ads, and each one whose copy is readable is now a charged result at the same price as a transcript. A keyword run that used to return a handful of video transcripts now returns roughly ten times as many charged rows. Use maxItems and the run's max total charge to set the size you want — or turn Read the text on image ads off to get the old video-only behaviour exactly as before.

You give itYou get back
Advertiser names (e.g. HubSpot)transcript — full speech-to-text, any length (first 3 minutes included, then a small per-minute surcharge)
…or keywords (e.g. crm software)hook3s — the ad's opening line, plus hookStartSeconds
…or just a market — countries: ["US"] + a date range, no keyword neededimageText — the headline, body and CTA read off an image ad
…or a pasted Ad Library search link, ad URLs or video URLsadvertiser, headline, adText, payingEntity, language, durationSeconds, timestamped segments
…or an Ad Library scraper run's dataset IDone JSON row per ad, misses clearly marked and never charged
…plus a watchlist name (optional)a separate list per competitor set — and, with New ads only, nothing you already paid for
…the same ads again, in any modeads already delivered to your account come back from that run, repeat: true, not charged

Output

One JSON row per ad. Delivered rows carry the transcript (or the creative's text) and charged: true; anything that could not be delivered carries a status saying why, and charged: false. One case delivers uncharged: if your max charge is reached while an ad is already being transcribed or read, that ad still ships in full with charged: false and a statusReason naming the cap — your cap is never exceeded, and work already done is never thrown away. Real rows from the live example run below (long text trimmed with … for the page):

statuschargedadvertiserheadlinehook3shookStartSecondstranscriptlanguagedurationSeconds
transcribedtrueSparkon | HubSpot Elite Partner | Growth, AI, CRM Implementations, RevOpsWe are Hubspot Elite PartnerIf your revenue team is working harder than ever, but closing less, the problem isn't effort, it's the system.0If your revenue team is working harder than ever, but closing less, the problem isn't effort, it's the system. Too many manual tasks, too many disconnected tools…English43.86
transcribedtrueSparkon | HubSpot Elite Partner | Growth, AI, CRM Implementations, RevOpsWe are Hubspot Elite PartnerWhen your CRM becomes too complex, every team pays the price.0When your CRM becomes too complex, every team pays the price. We connect your financial systems, automate workflows, and give every team clear operational visibility…English10.68
image_text_extractedtrueHubSpotnullnullnullimageText: "46% of marketers struggle with visual consistency. Get the free HubSpot & Canva Playbook"nullnull
no_audio_streamfalseHubSpotnullnullnullnullnullnull

LinkedIn ad transcripts output table — advertiser, headline, first-3s hook, transcript, language and the charged flag

Full row, as JSON:

{
"status": "transcribed",
"charged": true,
"advertiser": "Sparkon | HubSpot Elite Partner | Growth, AI, CRM Implementations, RevOps",
"headline": "We are Hubspot Elite Partner",
"adText": "Is your revenue team working harder, but closing less? ⚙️ …",
"payingEntity": "Paid for by Triario LLC",
"adId": "1508450294",
"detailUrl": "https://www.linkedin.com/ad-library/detail/1508450294",
"format": "SPONSORED_VIDEO",
"availability": "Ran from Aug 17, 2026 to Aug 21, 2026",
"impressions": "10k-20k",
"impressionsByCountry": [{ "country": "Germany", "share": "70%" }, { "country": "Switzerland", "share": "22%" }],
"hook3s": "If your revenue team is working harder than ever, but closing less,",
"hookStartSeconds": 0,
"transcript": "If your revenue team is working harder than ever, but closing less, the problem isn't effort, it's the system...",
"language": "English",
"durationSeconds": 43.9,
"segments": [{ "start": 0, "end": 7.7, "text": "If your revenue team is working harder than ever, but closing less, the problem isn't effort, it's the system." }],
"chargeEvents": { "transcript": 1, "surchargeMinutes": 0 }
}

availability, impressions and impressionsByCountry are LinkedIn's EU ad-transparency fields: they are populated only for ads that were shown in the EU (this sample ran in Germany and Switzerland) and are null on ads shown only elsewhere — a US-only campaign returns null in all three.

Browse the full live example dataset → — real output from a verified run, including the uncharged rows. Export with clean=true for spreadsheet-ready CSV; reconcile your invoice from the per-row charged flags immediately, or from the run's charged-event counter after it settles (~12 s).

An image ad's row looks the same, with imageText in place of the transcript:

{
"status": "image_text_extracted",
"charged": true,
"advertiser": "HubSpot",
"format": "SPONSORED_STATUS_UPDATE",
"adId": "1478297483",
"detailUrl": "https://www.linkedin.com/ad-library/detail/1478297483",
"imageUrl": "https://media.licdn.com/dms/image/v2/…",
"imageText": {
"headline": null,
"body": null,
"cta": null,
"displayUrl": null,
"rawText": "46% of marketers struggle with visual consistency. Get the free HubSpot & Canva Playbook HubSpot | Canva"
},
"chargeEvents": { "transcript": 1, "surchargeMinutes": 0 }
}

advertiser always comes from LinkedIn's own metadata, never from reading a logo in the picture.

Every field is always present (explicit null over silent omission). Non-delivered rows carry status + statusReason instead — ocr_no_text_found (a genuinely text-free creative, e.g. a plain stock photo), image_expired (most creative image links never expire, but a small share — 2 of 76 measured — are short-signed and can go stale in a chained dataset; re-run your scraper for fresh URLs, or search here so the link and the picture are read in the same run), image_skipped (you turned image reading off), non_video_skipped (document, carousel, article, message and text-only creatives pass through with their metadata), no_audio_stream (a genuinely silent video), no_speech (music-only audio), no_onscreen_text, not_found, blocked_by_challenge, unavailable_expired, no_videos_found, search_unavailable, asr_unavailable (the speech and text-reading service refused this actor's access mid-run — every ad left in the queue comes back as its own uncharged, retryable row instead of disappearing; re-run later), skipped_budget (the run reached your max charge before this ad's turn came at all — the row still carries the advertiser, format, ad ID and Ad Library link, and retryable: true, so you can raise the cap or pass the link in videoUrls next time; an ad already being transcribed or read when the cap was reached is delivered in full instead, uncharged; ads beyond your own maxItems are counted in the status line instead), no_creative_found, input_error, or failed_* (also listed in the run's ERRORS record). None of them are charged. availability and impressions are filled when LinkedIn shows them (ads shown in the EU) and null otherwise. The run's status line reconciles every ad it found: queued (with search results folded because they reuse one creative — one charge per creative), processed, left, and the cap that stopped it.

Every row carries "isNew": true for an ad your account had never answered before, "isNew": false for one it had, and "firstSeenAt", the moment it was first answered (this run's clock for a new ad, the original sighting for an old one). An ad you already had comes back with "repeat": true and "firstSeenRunId" naming the run that first delivered it, "charged": false, and its statusReason saying so.

Watch competitors on a schedule — pay only for new ads

You never pay for the same ad twice. Every run remembers the ads it answered for your account — in a key-value store called linkedin-ads-watch-account in your own Apify account — under every identity the ad has (its Ad Library ID, its creative's asset ID, its media URL). Run the same links, IDs, search or chained rows again and the ads you already have come back from the run that delivered them: repeat: true, firstSeenRunId, not fetched, not transcribed, not charged. The status line counts them (2 of these you already had: handed back, not charged) and OUTPUT.repeats holds the number. Delete that store to forget everything; entries older than 90 days are no longer treated as repeats.

Competitor research is rarely a one-off: the question is usually what did they launch since last week? A watchlist answers that with only the new ads.

  1. Put the advertisers you follow in Advertiser names (or keywords, a market filter, a pasted search link, or a chained scraper run — any input mode works).
  2. Give the run a Watchlist name, e.g. acme-competitors, and turn on New ads only.
  3. Save the input as a Task, put the Task on an Apify Schedule (daily works well for active advertisers), and add an integration or a webhook on run succeeded — a Google Sheet, Slack, n8n, Make, or your own endpoint.
{
"accountOwners": ["HubSpot", "Salesforce"],
"countries": ["US"],
"watchlistId": "crm-competitors",
"newAdsOnly": true
}

Each scheduled run then delivers only the ads the list has not answered before, each with isNew: true and its firstSeenAt. Ads already on the list are skipped before anything is fetched — not downloaded, not transcribed, not charged — and the run's status line says exactly what happened, for example: watchlist "crm-competitors": 4 new ads this run, 61 already on the list were skipped (not fetched, not charged); 65 on the list now. The same numbers sit in the run's OUTPUT record under watchlist, newAds and skippedSeen.

Some details worth knowing:

  • The list lives in your account, in a named key-value store called linkedin-ads-watch-<your name> (one record, SEEN). Open it in Storage → Key-value stores to see every ad the actor has answered under that name, or delete the record to start the list over. This actor runs with limited permissions and touches nothing else in your account.
  • Only answered ads go on the list. A delivered transcript counts, and so does a final uncharged verdict such as no speech. A failed download, a page that could not be fetched this minute, or an ad your cost cap left out is not remembered, so the next run tries it again.
  • Leave New ads only off and every ad runs as usual — you still get isNew / firstSeenAt on each row, which is the way to build a full "what is running now" picture while still knowing what is fresh.
  • New ads only without a watchlist name compares against your account's memory instead: ads already delivered to your account are skipped before anything is fetched. Only when neither a list nor the memory can be read does the run stop with one uncharged row, instead of charging you for ads you may already have. A named watchlist it cannot open stops the run the same way, before any spend.
  • If your account's memory cannot be read, the run still runs: it delivers and charges as usual, and the status line and the charged rows say the repeat check was unavailable (repeatCheck: "unavailable").
  • One ad, one sighting, however it arrives — the same ad found by two searches, or pasted as a link and found by search, is one entry and one charge; a creative served at a different bitrate is recognised as the same ad.

For AI agents & LLMs — copy this block

Actor: steadyfetch/linkedin-ads-transcript-scraper (Apify Store)
Does: LinkedIn Ad Library video ads -> full transcript + opening hook +
advertiser metadata. One JSON row per ad. No LinkedIn login or cookies.
Run: POST https://api.apify.com/v2/acts/steadyfetch~linkedin-ads-transcript-scraper/run-sync-get-dataset-items?token=APIFY_TOKEN
Input (any ONE target is enough — a market filter on its own counts as one):
{
"accountOwners": ["HubSpot"], // advertiser names; the actor finds their video ads
"keywords": ["crm software"], // or search the Ad Library by topic
"countries": ["US"], // market filter: 2-letter codes as the Ad Library lists
// them (GB, not UK) or "ALL". One search per country.
"dateOption": "last-30-days", // last-30-days | current-month | current-year |
// last-year | custom-date-range (+ startdate/enddate)
"payer": "", // optional: the organisation that paid for the ads
"impressionsMin": 0, // optional; EU-served ads only (LinkedIn publishes
"impressionsMax": 0, // impression counts for EU ads only)
"videoUrls": ["https://www.linkedin.com/ad-library/detail/1508450294",
"https://www.linkedin.com/ad-library/search?keyword=crm&countries=US"],
"datasetId": "<dataset id of any LinkedIn Ad Library scraper run>",
"watchlistId": "crm-competitors", // optional: remember answered ads under this name (your own account)
"newAdsOnly": false, // with a watchlistId: skip ads already on the list before any fetch
"includeImageText": true, // default ON: read image ads' copy (charged like a transcript)
"includeOnScreenText": false, // default OFF: read silent video ads' on-screen text
"includeNonVideo": false, // optional: uncharged rows for document/carousel/article/text ads
"maxItems": 1000 // optional cap — counts video AND image ads
}
Nulls every optional field above also accepts an explicit null, meaning "use the
default" — so a template that renders unset fields as null (n8n, agent
frameworks, chained workflows) runs exactly like one that omits them.
Output one row per ad: status, charged, isNew, firstSeenAt, advertiser, headline, adText, ctaText, payingEntity,
adId, detailUrl, format, availability, impressions, impressionsByCountry,
hook3s, hookStartSeconds, transcript, language, durationSeconds, segments[],
imageUrl, imageText{headline,body,cta,displayUrl,rawText}, onScreenText{...}
Pricing pay per event, platform usage INCLUDED, no start fee:
video-ad-transcript $0.020 -> $0.008 per delivered transcript OR image-ad text extraction
(falls with your Apify plan tier; an image never adds a surcharge)
long-video-surcharge $0.005 per started minute past the first 3 minutes
Charged only on delivery — creatives with no readable text, blocked pages and failures cost $0.
Ads skipped by a watchlist (newAdsOnly) are never charged.
~4 in 5 LinkedIn ads are image ads, so a keyword run charges for ~10x more rows than video alone.
Cap any run with maxTotalChargeUsd (minimum $0.05).
MCP: pin this actor in any MCP client with https://mcp.apify.com?tools=steadyfetch/linkedin-ads-transcript-scraper
(or ask Apify's MCP server for "linkedin ad transcripts").

Why this LinkedIn Ad Library transcript scraper?

  • It reads the whole library, not just the 7% that is video. The Ad Library has no format filter. This actor reads the format off each search result first, transcribes the video ads, and reads the on-image copy of the image ads — so a keyword search comes back with the ad copy of ~95% of what it saw instead of ~7%.
  • Browse a whole market, no keyword required. Set a country and a date range and the actor walks that market's recent ads — the way you would use the Ad Library's own filters, except every ad comes back as text. Useful when you don't yet know which advertisers or phrases to look for. Filters work alongside advertisers and keywords too, and LinkedIn applies them itself, so a filtered search is a genuinely different result set, not a trimmed one.
  • Paste the link you already have. Filtered the Ad Library by hand in your browser? Paste that linkedin.com/ad-library/search?… link into Ad URLs, video URLs or search links and the run inherits every filter in it.
  • Image ads cost no extra page loads. The creative image URL is already in the search result, so reading image ads adds zero requests to LinkedIn.
  • Charged only on delivery. Text either lands in your dataset or the row is not charged. Text-free stock photo → uncharged row that says so. Silent creative → uncharged row that says so. Blocked page → uncharged row that says so. No "it ran and charged me but returned nothing."
  • The run tells you what it saw. The status line names every format the search returned, including the ones this actor does not read — nothing is dropped silently.
  • Old datasets still work. LinkedIn ad video URLs are long-lived — unlike other ad platforms' CDN links, they don't expire in hours or days. A dataset you scraped weeks ago transcribes fine today.
  • No cookies, no login. Runs entirely on LinkedIn's public Ad Library — nothing to paste, no account at risk.
  • Any video length. Long-form B2B creatives transcribe fine — videos past 3 minutes add a small per-started-minute surcharge instead of failing.
  • Hook as data, and never blank. hook3s is the ad's first 3 seconds of speech — the line media buyers study — as its own field, with full segment timestamps beside it. LinkedIn creatives that open on a branded title card or music are common, so when nothing is said in the first 3 seconds you get the first 3 seconds of speech from wherever speech actually begins, and hookStartSeconds tells you which second that is (0 when the ad opens speaking). An ad with no speech at all leaves both null — this field is never an empty string, so "the ad opens silent" and "the ad opened talking, we lost it" can't be confused. Sort by hookStartSeconds to see which competitors make you wait for the pitch.
  • Works with any scraper's output. The dataset input deep-scans your rows for LinkedIn ad video links and ad detail links — no field mapping, no glue code.
  • Ad metadata included. Advertiser, headline, ad text, paying entity, run dates, and — where LinkedIn shows them (EU-targeted ads) — estimated impressions with the per-country split, on every row.

This actor may fail when the platform changes things — failed items are never charged.

How to transcribe LinkedIn video ads (no code)

  1. Type an advertiser name (e.g. HubSpot) into Advertiser names — or a topic into Keywords. Or leave both empty and set Countries (e.g. US) and a Date range under Market filters to browse that market's recent ads instead.
  2. Click Start. The actor searches the Ad Library, picks out the video ads, and transcribes each one.
  3. Each ad comes back as one JSON row: a transcript + hook for video ads, the on-image copy for image ads, plus advertiser metadata.
  4. Export as JSON/CSV, or read it via API.

Already scraping the Ad Library? Paste your run's dataset ID (or use Apify's Connect Actor integration) and this actor transcribes the video ads in it.

How much does it cost to transcribe LinkedIn ads?

One result = one ad's text payload — a video ad's transcript + hook + segments, or an image ad's on-image copy. Both cost the same event. The first 3 minutes of each video are included; longer videos add a small per-started-minute surcharge, and an image ad never does. No start fee, no subscription, no third-party API key needed.

JobApprox. cost
50 competitor ads (video or image)≈ $1.00
500 ads (a serious creative teardown)≈ $10
Apify free plan ($5 credit)250 ad creatives

Sizing a keyword run: roughly 4 in 5 LinkedIn ads are image ads and about 3 in 4 of those carry readable copy, so one page of 24 search results yields on the order of 15 charged rows. Set maxItems to the number of ads you actually want, and cap the run with max total charge — both stop the run honestly with a "what's left" message rather than overspending.

Platform usage (compute + transfer) is included in the event price — the price you see is the whole price. Searching, unreadable creatives, and failed fetches carry no result fee.

How often should you re-run this? Once an ad has been delivered to your account, this actor remembers it for 90 days: re-running the same advertisers, keywords or ad links hands those ads straight back from the run that first answered them, with no result fee — so a re-run only ever charges for ads it has not answered for you before. Re-running the same input an hour later therefore returns the same rows and adds no result fee. LinkedIn's Ad Library changes on the scale of days, not minutes, so a daily or weekly schedule is what actually surfaces new creatives. Turn on New ads only (newAdsOnly) if you would rather have the ads you already hold skipped as a named count than handed back as rows.

Works with — LinkedIn Ad Library scrapers you can chain from

The dataset input reads the values in each row (any dms.licdn.com video link or ad-library/detail/… link), not fixed field names — so it works with any Ad Library scraper, including:

ScraperChain by
silva95gustavo/linkedin-ad-library-scraperdataset ID or pasted rows (videoUrl / detail links)
dz_omar/linkedin-ads-scraperdataset ID or pasted rows (detailPageUrl, detail.videoUrls)
memo23/linkedin-ads-scraperdataset ID or pasted rows (adLibraryUrl + format: "VIDEO")
ivanvs/linkedin-ad-library-scraperdataset ID or pasted rows (url detail links)
automation-lab/linkedin-ad-library-scraperdataset ID or pasted rows (detailUrl, mediaUrl)

How to chain: run the scraper → copy the run's dataset ID into this actor's Dataset ID field (or use Apify's Connect Actor integration) → Start. Video rows are transcribed; image rows have their on-image copy read (turn Read the text on image ads off to pass them through uncharged instead); rows with no recognizable creative come back as uncharged no_creative_found. One caveat for chained rows: most creative image links never expire, but a small share are short-signed and can go stale in an older dataset — those rows come back as uncharged image_expired, and searching by advertiser/keyword here avoids the problem entirely. You can also skip the other scraper entirely — this actor searches the Ad Library itself.

Use it via API, MCP, and integrations

  • API: standard Apify run API — POST .../acts/steadyfetch~linkedin-ads-transcript-scraper/runs with { "accountOwners": ["HubSpot"] }. Python/Node clients work as with any actor.
  • MCP: one link pins this actor in Claude, Cursor, or any MCP client — https://mcp.apify.com?tools=steadyfetch/linkedin-ads-transcript-scraper — or ask Apify's MCP server for "linkedin ad transcripts".
  • n8n: a free ready-made template, no community nodes needed — LinkedIn Ad Library transcripts (one advertiser → hooks & transcripts → spreadsheet-shaped rows, 3-minute setup), listed on our profile website (the link on our Apify profile page).
  • Make / Zapier: call it as a regular Apify actor step right after your scraper node, or run it standalone with advertiser names.
  • Schedules + webhooks: save any input as a Task, schedule it, and use the Task's Integrations tab to push each run's new rows wherever they need to go.

FAQ

How does the watchlist know what I have already seen? Every ad the actor answers under a watchlist name is written to a key-value store in your own account (linkedin-ads-watch-<name>), keyed by the ad's Ad Library ID (or the creative's asset ID when there is no ID). The next run with that name reads the list first. Delete the store's SEEN record to start over. Nothing is stored anywhere else.

Does a watchlist run charge for the ads it skips? No. With New ads only on, an ad already on the list is dropped before anything is fetched; the status line and OUTPUT.skippedSeen count them, and no row and no charge is produced for them.

Where do the ads come from? LinkedIn's own Ad Library — the public transparency surface LinkedIn publishes so anyone can see who runs which ads. No login or cookies are used, ever.

Can I run it with no keyword — just a country? Yes. Set Countries and a Date range under Market filters and leave the advertiser and keyword fields empty: the actor browses that market's recent ads and returns them as text. LinkedIn applies the filters on its side, so this is a genuinely different result set from an unfiltered search. Leaving everything empty is still an error — the run needs at least one advertiser, keyword, market filter, link or dataset.

Why does each country cost a separate search? The Ad Library reads only the first country you give it, so listing three countries would silently answer for one. The actor runs one search per country instead — they all count against Max ads to process and your max charge, exactly like extra keywords do.

The impressions filter returns nothing. LinkedIn only publishes impression counts for ads shown in the EU, so filtering on impressions also limits results to EU-served ads. On a US-only market it legitimately comes back empty — the row says so and nothing is charged.

Why did some ads come back non_video_skipped? Document, carousel, article, message and text-only creatives are not read — there is no video to transcribe and no single ad image to read. They pass through uncharged with their advertiser/CTA metadata (turn on Include the remaining formats to get one uncharged metadata row for each one found in search).

How accurate is the image-ad text? It is read by a vision model from the ad's own creative, verbatim and untranslated, and delivered as imageText.rawText with the headline / body / CTA split out where the model can tell them apart. Non-English copy works. If nothing readable comes back, the row is ocr_no_text_found and uncharged — a plain product photo with no copy on it is never sold to you as a result.

Can I get the on-screen text of silent video ads? Yes — turn on Read on-screen text on silent video ads. About half of LinkedIn's video ads are silent or music-only, and most of them carry their whole message as on-screen type. With the switch on, those ads' frames are read and delivered as onScreenText, charged like a transcript; silent ads with no real copy (plain b-roll) stay uncharged. With the switch off — the default — a silent ad is never charged, exactly as before.

A search returned no_videos_found? The advertiser, keyword or market had no video ads and no image ads this actor could read — only document, carousel, article or message creatives, or nothing at all. The row tells you how many creatives were seen and of which formats, so you can widen the query. When you search several advertisers, keywords or countries at once, each search that turns up nothing readable gets its own uncharged row naming it — filters included, so a misspelled advertiser never disappears silently. Nothing is charged.

Silent video ads? Some B2B creatives ship with no audio track at all (text-overlay videos). We detect that and return an honest, uncharged no_audio_stream row instead of an empty transcript — or, with Read on-screen text on silent video ads turned on, we read the type off the video's own frames.

Music-only ads? Speech-recognition models hallucinate filler on music. We detect that and return an honest, uncharged no_speech row instead of selling you a fake transcript.

What is blocked_by_challenge / search_unavailable? LinkedIn occasionally serves a verification page instead of an ad page, or the Ad Library search returns nothing for a while. The actor retries, and if it still can't get through you get an uncharged row that says so — re-running a few minutes later usually picks those ads up.

Typos in bare ad IDs? A mistyped numeric ad ID is indistinguishable from a real one, so it surfaces as an honest, uncharged not_found row (LinkedIn answers "can't load this ad") rather than an input error.

Languages? Whisper-class multilingual ASR — German, French, Spanish, Arabic, Japanese, and ~90 more transcribe out of the box; the detected language ships on every row.

Is it legal to transcribe LinkedIn ads? The Ad Library is public by design (ad transparency, required by regulations like the EU DSA). This actor reads only that public surface, logged out. As always, how you use the data is on you.

Is this an official LinkedIn product? No. This is an independent actor from steadyfetch — unofficial, and not affiliated with, endorsed by, or sponsored by LinkedIn Corporation or Microsoft. "LinkedIn" is a trademark of its respective owner, used here only to name the public Ad Library surface this actor reads.

Steadyfetch ad-intelligence suite

One actor per ad surface, one job each, the same contract everywhere: all-inclusive pay-per-event, no start fee, charged only on delivery — and the same row shape, so a workflow built for one platform ports to the next.

Ad creatives → transcripts & on-creative text

Ad surfaceActorFree n8n template
Facebook & Instagram Ad LibraryFacebook Ads Transcript Scraper — Ad Library Video & Image AdsFacebook ad transcripts · Competitor ad teardown
Google Ads Transparency CenterGoogle Ads Video Transcript Scraper — Ads Transparency CenterGoogle Ads video transcripts
TikTok Creative Center Top AdsTikTok Ads Transcript Scraper — Creative Center Top Ads & HooksTikTok Top Ads transcripts
LinkedIn Ad Librarythis actorLinkedIn Ad Library transcripts
Instagram Reels (organic posts)Instagram Reel Transcript Scraper — Reels to Text with 3s HooksInstagram Reel transcripts

Ad creatives → text (image & text ads, read by OCR)

Ad surfaceActor
Google Ads Transparency CenterGoogle Ads Transparency Scraper — Ad Copy, CTAs & Image OCR

Free n8n workflow templates for this actor are listed on our profile website (link on our Apify profile).

The rest of the steadyfetch shelf — same contract everywhere: all-inclusive pay per event, no start fee, charged only on delivery.

FamilyActors
Ad creative intelligenceFacebook · Google Ads video · TikTok · LinkedIn · Google Ads text & OCR
Trends & keywordsGoogle Trends · Trends Now · Breakout keywords · Autocomplete keywords · Keyword volume & CPC · Social trends
YouTube transcriptsYouTube videos · YouTube channels
InstagramReel transcripts · Profile posts
JobsIndeed · Career sites by domain · Glassdoor · Multi-board · Google Jobs
AmazonProducts · Search · Bestsellers · Sellers
Any media fileSpeech to Text · any link or file

Feedback & support

Found an issue? Open it on the Issues tab — we usually reply within a couple of hours, always within a day. Feature requests welcome. And if this actor earned its keep, a rating helps other buyers find it, and saving it keeps it one click away.