# TikTok Ads Library Scraper — EU Ad Library & Top Ads (`latentlark/tiktok-ads-library`) Actor

Scrape TikTok's EU Ad Library (advertiser, ad text, video, first/last shown, reach by country, targeting) and Creative Center Top Ads (CTR, likes, industry, objective). Only-new monitor mode, advertiser summary. Pay per ad.

- **URL**: https://apify.com/latentlark/tiktok-ads-library.md
- **Developed by:** [LatentLark](https://apify.com/latentlark) (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 $1.05 / 1,000 ads

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## TikTok Ads Library Scraper — EU Ad Library & Top Ads (Creative Center)

**For performance marketers, agencies and ad-spy / competitor-research teams.** Pull competitors' TikTok ads from **both** of TikTok's public ad sources in one Actor:

- 🇪🇺 **EU Ad Library** (library.tiktok.com): every ad shown in the EU/EEA, Switzerland, the UK and Turkey, with advertiser, ad text, video, **first/last shown**, **reach by country** and **age/gender/interest targeting**, landing page and call to action.
- 🏆 **Creative Center Top Ads** (ads.tiktok.com): TikTok's curated best-performing ads in the **US** and 27 other markets, with **CTR**, likes, industry, objective, landing page and video in up to 1080p.

**Outcome:** a clean table of what your competitors run on TikTok right now, plus a weekly "only new ads" feed for monitoring, with no login, no TikTok account and no residential proxy.

- 🔎 **Search by keyword or advertiser** (name, `Name|bizId` or an advertiser link). Exact phrases with `"quotes"`.
- ♾️ **Past TikTok's ceilings:** automatic query slicing beyond the EU Library's short pages and Top Ads' 20-ads-per-filter anonymous cap, deduped by ad id.
- 🔔 **Monitor mode:** `onlyNewSinceLastRun` returns only ads you have not seen before (per `monitorKey`). Skipped ads are free.
- 📊 **Advertiser summary rows:** ad count, new ads, countries, industries and landing domains per advertiser.
- 🟢 **Soft fail:** if TikTok rate-limits or nothing is found, the run still **SUCCEEDS** with a free `status` row explaining why, so schedules stay green.
- 💸 **Pay per ad:** $1.50 per 1,000 ads (+$1.00 per 1,000 with details).

### Quick start (first run in about 30 seconds)

1. Click **Try for free** (or **Start** in Console). The form is prefilled with source **EU Ad Library**, keyword `nike`, country `DE`, last 30 days and **10 ads max**, details on.
2. Click **Start**. In about a minute you get ~10 Nike ads shown in Germany (advertiser, ad text, video, cover, first/last shown, reach in DE, targeting, landing page) plus an advertiser summary row, for about **$0.03**.
3. Swap in your competitors' names or keywords and countries, raise **Max ads per keyword/advertiser** (default 200), or switch **Source** to **Top Ads** for US creatives.

### What you can do with it

- **Competitor ad spy:** every TikTok ad a brand ran in Europe in the last 7/30/90/365 days, with the landing pages and offers they push.
- **Creative research:** Top Ads by CTR or likes for your industry (e.g. *Apparel & Accessories*, *Beauty & Personal Care*) and objective (*Conversions*, *Traffic*…) to brief UGC creators.
- **Weekly new-ads alerts:** schedule the Actor with `onlyNewSinceLastRun` and pipe new rows to Slack, email or Google Sheets.
- **Targeting and reach intel (EU):** which countries, ages and genders a competitor targets and the estimated reach per country.
- **Lead generation for agencies:** brands actively spending on TikTok ads in a market, with their landing domains.
- **Straight into a spreadsheet:** send the dataset to Google Sheets with [Google Sheets Export](https://apify.com/latentlark/sheets-export).

### Input

| Field | What it does |
|---|---|
| `source` | `euLibrary` (default), `topAds` or `both` |
| `keywords` | EU: advertiser name or ad text (`nike`, `meal kit`); `"quotes"` = exact phrase. Top Ads: only used with `topAds.matchKeywords` |
| `advertisers` | EU only: advertiser name (exact legal name works best, e.g. `NIKE Retail B.V.`), `Name\|bizId`, or an advertiser link from library.tiktok.com |
| `countries` | EU: EU/EEA + CH, GB, TR (empty = all). Top Ads: US, GB, DE, FR, JP, BR… or `ALL` (empty = US) |
| `dateRange` | EU: `last7days`, `last30days` (default), `last90days`, `lastYear` or `custom` with `startDate`/`endDate` |
| `onlyActive` | EU: only ads still running |
| `topAds` | `{period: 7\|30\|180, sortBy: for_you\|ctr\|like\|impression\|play_6s_rate\|cvr, industry, objective, adLanguage, slice, matchKeywords}` |
| `maxAdsPerQuery` | Max ads per keyword/advertiser × country (EU) or per country × period × filter (Top Ads). Default 200, prefilled 10 |
| `includeDetails` | Landing page, CTA, objective, reach by country, targeting (EU); landing page, countries, comments/shares (Top Ads). Default on |
| `onlyNewSinceLastRun` + `monitorKey` | Monitor mode: only ads not seen in earlier runs for this key |
| `outputAdvertiserSummary` | One summary row per advertiser (default on) |
| `proxyConfiguration` | Apify datacenter proxy (default). Residential is not needed |

**EU example** (two competitors, three countries, last 90 days):

```json
{
  "source": "euLibrary",
  "advertisers": ["NIKE Retail B.V."],
  "keywords": ["adidas"],
  "countries": ["DE", "FR", "IT"],
  "dateRange": "last90days",
  "maxAdsPerQuery": 100,
  "includeDetails": true
}
```

**Top Ads example** (US e-commerce, best CTR, last 30 days):

```json
{
  "source": "topAds",
  "countries": ["US"],
  "topAds": { "period": 30, "sortBy": "ctr", "industry": "Apparel & Accessories" },
  "maxAdsPerQuery": 50
}
```

### Output

One dataset item per ad (`rowType: "ad"`). **EU Ad Library** row (with details):

```json
{
  "rowType": "ad",
  "source": "euLibrary",
  "adId": "1879578150853938",
  "advertiserName": "NIKE Retail B.V.",
  "advertiserBizId": "6876453864464188162",
  "adText": "Neue Winterausrüstung in Nike Factory Stores.",
  "videoUrl": "https://library.tiktok.com/api/v1/cdn/…/video/…",
  "coverImageUrl": "https://p16-common-sign.tiktokcdn.com/…",
  "landingUrl": "https://www.nike.com/de/retail?cp=…",
  "landingDomain": "nike.com",
  "firstShown": "2026-10-20T00:00:00Z",
  "lastShown": "2026-10-25T00:00:00Z",
  "daysActive": 6,
  "countries": ["DE"],
  "reachTotal": "200K-300K",
  "reachByCountry": { "DE": "209K" },
  "targeting": {
    "age": [{ "region": "DE", "18-24": true, "25-34": true, "35-44": true, "45-54": false }],
    "gender": [{ "region": "DE", "female": true, "male": true }],
    "target_audience_size": "15.2M-18.6M",
    "provinces": ["Berlin"]
  },
  "industry": "Apparel & Accessories",
  "objective": "Traffic",
  "callToAction": "Learn more",
  "tiktokUsername": "nike",
  "advertiserRegistryLocation": "Netherlands",
  "adLibraryUrl": "https://library.tiktok.com/ads/detail/?ad_id=1879578150853938",
  "detailsFetched": true,
  "isNew": true,
  "sourceInput": "nike",
  "scrapedAt": "2026-10-26T22:05:25+00:00"
}
```

**Top Ads** row (with details):

```json
{
  "rowType": "ad",
  "source": "topAds",
  "adId": "7685323149223460884",
  "advertiserName": null,
  "adText": "Ekorren Schlafsofa",
  "videoUrl": "https://v16m-default.tiktokcdn.com/…",
  "landingUrl": "https://www.sweef.de?utm_source=tiktok…",
  "landingDomain": "sweef.de",
  "countries": ["DE", "SE"],
  "ctr": 0.78,
  "likes": 0,
  "industry": "Furniture",
  "industryParent": "Home Improvement",
  "objective": "Conversions",
  "videoDurationSec": 30.9,
  "topAdsPeriodDays": 30,
  "topAdsUrl": "https://ads.tiktok.com/business/creativecenter/topads/7685323149223460884/pc/en",
  "detailsFetched": true,
  "isNew": true,
  "sourceInput": "DE / 30d"
}
```

**Advertiser summary** row (`rowType: "advertiserSummary"`):

```json
{ "rowType": "advertiserSummary", "source": "euLibrary", "groupedBy": "advertiser", "advertiserName": "NIKE Retail B.V.", "advertiserBizId": "6876453864464188162", "adCount": 30, "newAdCount": 30, "firstShown": "2026-10-14T00:00:00Z", "lastShown": "2026-10-25T00:00:00Z", "countries": ["DE"], "industries": ["Apparel & Accessories"], "landingDomains": ["nike.com"] }
```

- **Status** rows (`rowType: "status"`, free) explain soft failures: `reason` (e.g. `eu_library_busy`, `top_ads_rate_limited`, `top_ads_no_results`, `top_ads_unknown_filter`, `advertiser_not_resolved`), a `hint`, and `retry: true/false`.
- **Storage → Key-value store → SUMMARY**: per keyword/country ads found, slices used, server totals, errors.
- Dataset view **Ads** shows a table with cover thumbnails.

### Pricing

Pay per event, with volume discounts on higher Apify plans:

| Event | What it is | Free plan | Bronze | Silver | Gold+ |
|---|---|---|---|---|---|
| `apify-actor-start` | Once per run (per GB of memory; default 512 MB = one start) | $0.005 | $0.005 | $0.005 | $0.005 |
| `ad` | One ad saved to the dataset | $0.0015 | $0.00135 | $0.0012 | $0.00105 |
| `ad-details` | Extra detail fetch that succeeded for an ad (`includeDetails`) | $0.001 | $0.0009 | $0.0008 | $0.0007 |
| `advertiser-summary` | One advertiser summary row | $0.003 | $0.0027 | $0.0024 | $0.0021 |

- **Free:** status rows, ads skipped by only-new mode, duplicates, and details that failed to load.
- Examples (Free plan): 10 ads with details + 1 summary ≈ **$0.04**; 1,000 EU ads without details ≈ **$1.51**; 1,000 ads with details ≈ **$2.51** plus summary rows.
- Set **Max cost per run** in Console to cap spend; the Actor stops cleanly at the limit.

### How it works and limitations

- **EU Ad Library covers Europe only.** It is TikTok's DSA transparency archive: ads shown in the EU/EEA, Switzerland, the UK and Turkey. **For US creatives use `source: "topAds"`.**
- **Top Ads is a curated sample, not a full library.** Anonymous Creative Center access returns at most 20 ads per (country, period, sort order, industry, objective). With `slice: true` (default) the Actor combines sort orders and industry/objective sub-filters to go further (e.g. 142 of 143 US ads for 30 days). TikTok's pool itself tops out around 400 ads for 180 days or `ALL` countries.
- **Top Ads brand is often empty.** Creative Center hides `brand_name` for many ads, so `advertiserName` can be `null`. Use `landingDomain` to identify the brand; advertiser summaries for Top Ads are grouped by landing domain (2+ ads per group).
- **Top Ads has no keyword search without a login.** `topAds.matchKeywords: true` filters the fetched Top Ads by your keywords client-side.
- **EU short pages.** TikTok's EU search often stops after ~50 ads per query; the Actor re-requests, flips sort orders, halves date windows and splits by country to go further, deduping by ad id.
- **"System busy" (HTTP 421).** TikTok's EU Library answers some requests with 421; the Actor re-mints its session token and retries automatically (about 1 in 5 requests on datacenter proxies, all recovered in tests). Persistent 421s end with a free `eu_library_busy` status row.
- Reach and targeting come from the EU detail page and are TikTok's own estimates (ranges like `200K-300K`).
- Video and image URLs are signed TikTok CDN links that expire after some days; download what you need soon after the run.
- Public data only: no login, no TikTok account, no private data.

### FAQ

**I got 0 results.** Check the source and countries first: EU Ad Library only has ads shown in Europe (a US-only brand returns nothing; use Top Ads). Try a wider `dateRange` (`last90days`), a shorter keyword (`nike` instead of `nike air max 90 sale`), or remove quotes. For advertisers, use the exact legal name shown in the library (e.g. `NIKE Retail B.V.`, not `Nike`) or paste the advertiser link. For Top Ads, try `period: 180`, a parent industry, or fewer filters. The `status` row and the SUMMARY record say what happened.

**Why is `advertiserName` empty on Top Ads?** TikTok does not show the brand for many Creative Center ads. Use `landingDomain` and `topAdsUrl`.

**Can I get US ads with reach and targeting?** No public TikTok source has that for the US. EU ads have reach/targeting; US ads are available only as Top Ads (CTR, likes, industry, objective).

**How do I monitor a competitor weekly?** Turn on **Only new ads since last run**, set a **Monitor key** (e.g. `nike-weekly`), and create a schedule. Each run returns only ads not seen before for that key.

**Some runs show `top_ads_rate_limited` or `eu_library_busy`.** TikTok throttled the requests. Partial results are kept, the run succeeds, and you can re-run later.

### More from LatentLark

- [Google Ads Transparency & LinkedIn Ad Library Scraper](https://apify.com/latentlark/competitor-ads-monitor): the same competitor monitoring for Google Search/Display/YouTube and LinkedIn ads.
- [Google Sheets Export & Import (No OAuth)](https://apify.com/latentlark/sheets-export): send any dataset to Google Sheets.
- [Google Local Services Ads Scraper](https://apify.com/latentlark/google-local-services-ads): which local pros pay for Google Local Services Ads.
- [Facebook Reels Scraper + Transcripts](https://apify.com/latentlark/facebook-reels-scraper): public Facebook Reels with plays, reactions, shares and a spoken-word transcript (captions or Whisper), no login.

# Actor input Schema

## `source` (type: `string`):

**EU Ad Library** = TikTok's DSA transparency archive: every ad shown in the EU/EEA, Switzerland, the UK and Turkey, with first/last shown, reach by country and targeting. **Top Ads** = TikTok Creative Center's curated best performers (US and 27 other markets) with CTR, likes, industry and objective. **Both** runs them one after the other.

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

EU Ad Library: searched in advertiser name and ad text (e.g. `nike`, `meal kit`). Wrap in double quotes for an exact phrase (`"hello fresh"`). Top Ads: ignored unless `matchKeywords` is true in Top Ads filters (keyword search on Creative Center needs a login, so we filter client-side).

## `advertisers` (type: `array`):

EU Ad Library only. Advertiser name (resolved via TikTok's advertiser suggestions; exact legal name works best, e.g. `NIKE Retail B.V.`), `Exact Name|bizId`, or an advertiser link from an ad detail page (`library.tiktok.com/ads?adv_name=...&adv_biz_ids=...`). Returns only that advertiser's ads.

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

2-letter codes. EU Ad Library: EU/EEA + CH, GB, TR (empty = all regions). Top Ads: one of 28 markets such as US, GB, DE, FR, JP, BR, or `ALL` (empty = US). US ads are only available from Top Ads.

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

Ads shown in this window. 'custom' uses startDate/endDate.

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

YYYY-MM-DD, used when dateRange = custom.

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

YYYY-MM-DD, used when dateRange = custom.

## `onlyActive` (type: `boolean`):

Only ads that are still running (EU Library ad_status=1).

## `topAds` (type: `object`):

Creative Center Top Ads options. {period: 7|30|180 or a list, sortBy: for_you|ctr|like|impression|play_6s_rate|cvr, industry: label (e.g. "Beauty & Personal Care") or numeric id, or a list, objective: label (e.g. "Conversions") or id, or a list, adLanguage: e.g. "en", slice: true (default; combines sort orders and industry/objective sub-filters to get past TikTok's 20-ads-per-filter limit), matchKeywords: false (true = keep only Top Ads whose text/brand contains one of the keywords; TikTok's keyword search needs a login)}. Countries for Top Ads: 28 markets (US, GB, DE, FR, JP, BR, ...) or ALL.

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

EU Ad Library: per keyword/advertiser and country. Top Ads: per country × period × industry/objective. The Actor auto-slices past TikTok's per-query ceilings (EU short pages, Top Ads' 20-ads anonymous cap) and dedupes by ad id. Keep it low for a test run.

## `includeDetails` (type: `boolean`):

One extra request per ad: landing page, call to action, objective, advertiser business ID and TikTok handle, reach by country and age/gender/interest targeting (EU); landing page, countries, comments/shares, keywords (Top Ads). Charged as `ad-details` only when the detail fetch succeeds.

## `detailSessions` (type: `integer`):

Parallel EU sessions, one per proxy IP, used for detail fetches. Ignored without a proxy.

## `onlyNewSinceLastRun` (type: `boolean`):

Monitor mode: skip ads already returned for this Monitor key in earlier runs (skipped ads are not charged). Schedule it weekly to get only new competitor ads.

## `monitorKey` (type: `string`):

Name of the saved state for only-new mode, one per watchlist (e.g. `nike-weekly`). Leave empty to use `default`. Use a different key per schedule, or two schedules will share (and consume) the same seen-ads list.

## `outputAdvertiserSummary` (type: `boolean`):

Add one `advertiserSummary` row per advertiser (ad count, new ads, countries, industries, landing domains). Top Ads often has no brand name, so those ads are grouped by landing domain and only groups with 2+ ads get a row. Charged as `advertiser-summary`.

## `proxyConfiguration` (type: `object`):

Apify datacenter proxy works for both sources (tested). Residential is not needed.

## Actor input object example

```json
{
  "source": "euLibrary",
  "keywords": [
    "nike"
  ],
  "countries": [
    "DE"
  ],
  "dateRange": "last30days",
  "onlyActive": false,
  "topAds": {
    "period": 30,
    "sortBy": "for_you"
  },
  "maxAdsPerQuery": 10,
  "includeDetails": true,
  "detailSessions": 3,
  "onlyNewSinceLastRun": false,
  "outputAdvertiserSummary": true,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

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

No description

# 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 = {
    "source": "euLibrary",
    "keywords": [
        "nike"
    ],
    "countries": [
        "DE"
    ],
    "dateRange": "last30days",
    "topAds": {
        "period": 30,
        "sortBy": "for_you"
    },
    "maxAdsPerQuery": 10,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("latentlark/tiktok-ads-library").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 = {
    "source": "euLibrary",
    "keywords": ["nike"],
    "countries": ["DE"],
    "dateRange": "last30days",
    "topAds": {
        "period": 30,
        "sortBy": "for_you",
    },
    "maxAdsPerQuery": 10,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("latentlark/tiktok-ads-library").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 '{
  "source": "euLibrary",
  "keywords": [
    "nike"
  ],
  "countries": [
    "DE"
  ],
  "dateRange": "last30days",
  "topAds": {
    "period": 30,
    "sortBy": "for_you"
  },
  "maxAdsPerQuery": 10,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call latentlark/tiktok-ads-library --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,latentlark/tiktok-ads-library"
        }
    }
}
```

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/M9qea7VglwmnkCof9/builds/ZhNnAO9C2u7MNk5YV/openapi.json
