# TikTok Ad Library Scraper - Reliable (`atlasdata/tiktok-ad-library-scraper-reliable`) Actor

Scrape the TikTok Ad Library (EU/EEA, UK, CH, TR) by advertiser, keyword, country, date, status, format, reach, age and gender. Fast cards or full details: landing page, CTA, objective, paid by, TikTok account, impressions by country/age/gender, targeting.

- **URL**: https://apify.com/atlasdata/tiktok-ad-library-scraper-reliable.md
- **Developed by:** [Christian Machon](https://apify.com/atlasdata) (community)
- **Categories:** Marketing, Lead generation, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.40 / 1,000 ad scrapeds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/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 & API - Reliable

Scrape the public [TikTok Ad Library](https://library.tiktok.com/ads) (TikTok's Commercial Content Library) by **advertiser name or keyword, country, date range, ad status, format, reach, targeted age and gender**. Get every ad's text, video / image, advertiser, first and last shown date and unique reach, and optionally the full **ad details: landing page URL, call to action, objective, "paid by", TikTok account, impressions by country with an age x gender split, and targeting** (countries, regions, ages, genders, audience size, interests, languages, devices).

It runs a real Chrome browser, because TikTok signs every Ad Library API request in the page (plain HTTP requests get "system busy" errors). No login, no cookies, no proxy needed: if TikTok starts blocking the server's IP, the Actor switches to Apify Proxy sessions by itself.

### What can you use it for?

- **Competitor ad research**: every ad a brand has run on TikTok in Europe, with video, copy and landing page.
- **Creative inspiration**: browse current video ads for a keyword ("skincare", `"black friday"`) and sort by reach.
- **Reach and spend proxies**: estimated unique reach and impression ranges per country, age and gender.
- **Targeting intelligence**: which countries, regions, ages, genders, interests and devices an advertiser targets.
- **Lead generation**: find companies actively advertising on TikTok in a market, with their advertiser ID, registered country and TikTok account.

### Two modes: cards (fast) or details (full)

| | Cards (`includeDetails: false`, default) | Details (`includeDetails: true`) |
|---|---|---|
| What you get | Ad ID/URL, advertiser name, ad text, video / cover / images, first & last shown, unique reach, industry | Everything in cards **plus** landing page, CTA, objective, advertiser ID & country, paid by, TikTok account, impressions by country / age / gender, targeting |
| Speed (our tests) | about **150-350 ads/minute** per search; **500-620 ads/minute** with several searches in parallel | about **25-35 ads/minute** (about 8-15/minute when TikTok blocks the IP and the run moves to proxies) |
| Price | $0.40 per 1,000 ads | $1.50 per 1,000 ads |

Details are slower because TikTok serves only **one detail record per full page load**, so each ad needs its own page load. Use cards for volume and details when you need landing pages, impressions or targeting.

### How to use it

1. Enter one or more **search terms**: an advertiser name or keyword, exactly like the search box on library.tiktok.com. Wrap a term in double quotes (`"free shipping"`) for an exact phrase. Each term is a separate search.
2. Optionally pick **countries** (the Ad Library covers only the EU/EEA, Switzerland, the UK and Turkey), a **date range**, **ad status**, **format**, **reach**, **ages**, **gender** and **sort order**.
3. Set **Max ads per search** (TikTok serves 12 per page and shows at most about 5,000 per search).
4. Switch **Include ad details** on if you need the full record.
5. Run. Ads go to the dataset; a per-input summary goes to the `OUTPUT` record in the key-value store.

You can also paste **Ad Library search URLs** (all their filters are kept), **single ad URLs** (`https://library.tiktok.com/ads/detail/?ad_id=...`) or plain **ad IDs**; single ads are always returned with details.

### Input examples

Cards only, two brands in Germany and France, last 30 days:

```json
{
  "searchTerms": ["nike", "adidas"],
  "countries": ["DE", "FR"],
  "dateRange": "last-30-days",
  "maxAdsPerSearch": 200
}
```

Full details for active video ads with an exact phrase, sorted by reach:

```json
{
  "searchTerms": ["\"black friday\""],
  "countries": ["GB"],
  "adStatus": "active",
  "mediaType": "video",
  "sortBy": "reach-desc",
  "maxAdsPerSearch": 50,
  "includeDetails": true
}
```

Specific ads:

```json
{
  "adIds": ["1863828861691042"],
  "adLibraryUrls": ["https://library.tiktok.com/ads/detail/?ad_id=1848312888172689"]
}
```

| Field | Description |
|---|---|
| `searchTerms` | Advertiser names or keywords; `"quoted"` = exact phrase. |
| `adLibraryUrls` | Ad Library search URLs or single ad URLs. |
| `adIds` | Numeric TikTok ad IDs (returned with details). |
| `countries` | ISO codes: AT BE BG CH CY CZ DE DK EE ES FI FR GB GR HR HU IE IS IT LI LT LU LV MT NL NO PL PT RO SE SI SK TR. Empty = all. Other codes are reported in `OUTPUT.inputErrors`. |
| `dateRange` | `last-7-days`, `last-30-days`, `last-90-days`, `last-12-months` (default), `all` or `custom` with `startDate` / `endDate`. |
| `adStatus` | `all`, `active`, `inactive`. |
| `mediaType` | `all`, `video`, `image`, `text`. |
| `reach` | `under-10k`, `10k-100k`, `over-100k`. |
| `ages`, `gender` | Targeted age groups and gender. |
| `sortBy` | Last shown, published date or reach, ascending or descending. |
| `maxAdsPerSearch` | 1-5000, default 50. |
| `includeDetails` | Default `false` (cards). |
| `maxConcurrency` | Parallel browser tabs, default 3 (fits 2 GB). For large detail runs use 4 GB and 4. |
| `maxRetries` | Retries per search page / detail record, default 6. |
| `proxyConfiguration` | Not needed. Set it only to force every request through a proxy. |

### What data you get

Fill rates **measured on 4,979 ads from our final test runs** (October 2026): 4,670 card-only ads and 309 ads with details.

| Field | Cards | Details | Notes |
|---|---|---|---|
| `adId`, `adUrl`, `adText`, `mediaType`, `firstShown`, `lastShown`, `removed`, `searchInput`, `searchType`, `detailsStatus`, `scrapedAt` | 100% | 100% | Dates are `YYYY-MM-DD`. |
| `advertiserName` | 96% | 92% | |
| `estimatedAudience`, `estimatedAudienceMin`, `estimatedAudienceMax` | 96% | 100% | Unique reach, e.g. "10K-100K" → 10000 / 100000. |
| `videoUrl`, `videoCoverUrl`, `videoCount` | 79% | 80% | Video ads (about 79% of ads). Video links are signed by TikTok and expire. |
| `imageUrls` | 49% | 43% | Image ads and carousels. |
| `industry` | 29% | 85% | |
| `objective` | - | 99% | E.g. "Community interaction", "Traffic". |
| `advertiserId`, `advertiserCountry`, `paidBy` | - | 91-92% | |
| `callToAction` | - | 61% | |
| `landingPageUrl` | - | 43% | Ads that link off TikTok. |
| `tiktokUsername`, `tiktokDisplayName`, `tiktokProfileUrl`, `tiktokFollowers`, `tiktokFollowersCount`, `tiktokAccountType`, `tiktokAvatarUrl` | - | 32% | Only when TikTok links the ad to a TikTok account. |
| `totalImpressions` (+ `Min` / `Max`), `impressionsByCountry` | - | 100% | Ranges per country with `byAgeGender` split. |
| `targetCountries`, `targetAges`, `targetGenders`, `targetAudienceSize`, `targetCustomAudience`, `targetAudienceExclude`, `targetHighSpendingPower`, `targetOperatingSystems` | - | 99-100% | |
| `targetLanguages`, `targetInterests`, `targetProvinces`, `targetCities` | - | 1-10% | Only when the advertiser used them. |
| `rejectionInfo` | 0% | 8% | For removed ads. |

`detailsStatus` is `OK`, `NOT_REQUESTED` (cards mode) or `FAILED` (the detail record could not be loaded after all retries; the card data is still saved and the details are not charged).

Not published by TikTok and therefore **not** in the output: exact spend, exact impressions, engagement (likes, comments), and ads shown only outside Europe.

### Output example (details mode, shortened)

```json
{
  "adId": "1856680845765634",
  "adUrl": "https://library.tiktok.com/ads/detail/?ad_id=1856680845765634",
  "advertiserName": "buildlegovibes",
  "adText": "#afol #lego #harrypotter @LEGO",
  "industry": "Household Products",
  "mediaType": "video",
  "videoUrl": "https://library.tiktok.com/api/v1/cdn/.../video/...",
  "videoCoverUrl": "https://p16-common-sign.tiktokcdn.com/...",
  "firstShown": "2026-02-09",
  "lastShown": "2026-02-12",
  "estimatedAudience": "0-1K",
  "estimatedAudienceMin": 0,
  "estimatedAudienceMax": 1000,
  "objective": "Community interaction",
  "advertiserId": "7456118493550002193",
  "advertiserCountry": "Netherlands",
  "totalImpressions": "0-1K",
  "impressionsByCountry": [
    { "country": "ES", "impressions": "0-1K", "impressionsMin": 0, "impressionsMax": 1000,
      "byAgeGender": [{ "age": "18-24", "gender": "FEMALE", "impressions": "-" }] }
  ],
  "targetCountries": ["ES", "LU"],
  "targetAges": [{ "country": "ES", "ages": ["18-24", "25-34"] }],
  "searchInput": "lego",
  "searchType": "searchTerm",
  "detailsStatus": "OK",
  "scrapedAt": "2026-10-26T23:40:12.512Z"
}
```

#### Run summary (`OUTPUT` record)

Each input gets one status: `SUCCEEDED`, `PARTIAL`, `NO_DATA`, `FAILED` or `DUPLICATE`, with ad counts, the number TikTok reports, errors and network stats. If every input fails, the run is marked as failed, so you are never left with an empty "successful" run.

### Pricing

Pay per event, no subscription:

| Event | Price | When |
|---|---|---|
| Actor start | $0.001 per GB of memory ($0.002 at the default 2 GB) | Once per run |
| Ad | $0.0004 | Every ad in the dataset |
| Ad details | $0.0011 | Only for ads whose detail record loaded (`detailsStatus: "OK"`) |

That is **$0.40 per 1,000 ads (cards)** and **$1.50 per 1,000 ads with full details**, plus the start fee. Searches with no results cost only the start fee. Apify's "Maximum cost per run" is respected: the scraper stops cleanly when it is reached.

Examples: 1,000 cards ≈ $0.40 and about 3 minutes; 100 ads with details ≈ $0.15 and about 3-4 minutes.

### FAQ

**Do I need a TikTok account, cookies or a proxy?** No.

**Why only European countries?** TikTok's Ad Library is a transparency tool under the EU Digital Services Act and only covers ads shown in the EU/EEA, Switzerland, the UK and Turkey.

**Why do I get fewer ads than TikTok's total?** TikTok shows at most about 5,000 ads per search, and its total is approximate. Split large searches by country or date range.

**Is the search exact?** Plain terms use TikTok's own relevance search (advertiser names and ad text). Use `"quotes"` for an exact phrase, or paste an advertiser's search URL from library.tiktok.com.

### Limits and honest caveats

- **Details are slow by design**: one page load per ad (about 25-35 ads/minute). Use cards mode for volume.
- **IP blocks**: TikTok sometimes blocks a server IP after many detail page loads. The Actor then moves to Apify datacenter proxy sessions automatically; those runs are about half as fast but still complete. If no proxy is available and details keep failing, remaining ads are saved as cards (`detailsStatus: "FAILED"`, not charged for details).
- **Search quirks**: TikTok sometimes returns duplicates across pages (we remove them) and short pages that claim there are no more results (we keep paging). Sorting by date is mixed with relevance for keyword searches.
- **Test results**: on the final test matrix for the current build (32 runs covering cards and details, 1-4 searches, 25 countries, all filters, pasted URLs, ad IDs, invalid input, an empty search, and 1,000-ad and 1,200-ad runs), **32 of 32 runs succeeded**. In 3 of them TikTok blocked the server's IP (once before the first page loaded); each run switched to Apify Proxy by itself and still returned every ad with details.

# Actor input Schema

## `searchTerms` (type: `array`):

Each entry is one search, exactly like the search box on library.tiktok.com: it matches advertiser names and ad text. Wrap a term in double quotes ("black friday") for an exact-phrase search.

## `adLibraryUrls` (type: `array`):

Optional. Paste search URLs (https://library.tiktok.com/ads?region=...\&adv_name=...) - all their filters are kept - or single ad URLs (https://library.tiktok.com/ads/detail/?ad_id=...).

## `adIds` (type: `array`):

Optional. Numeric TikTok ad IDs to look up directly (full details for each).

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

ISO codes. The TikTok Ad Library only covers the EU/EEA, Switzerland, the UK and Turkey: AT BE BG CH CY CZ DE DK EE ES FI FR GB GR HR HU IE IS IT LI LT LU LV MT NL NO PL PT RO SE SI SK TR. Leave empty for all of them. Other codes are ignored and reported in OUTPUT.inputErrors.

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

Only ads shown in this period. Not applied to pasted search URLs, which keep their own dates.

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

YYYY-MM-DD. Used only when Date range = Custom.

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

YYYY-MM-DD. Defaults to today.

## `adStatus` (type: `string`):

Active = still running.

## `mediaType` (type: `string`):

Filter by creative format.

## `reach` (type: `array`):

Optional. Only ads with this estimated unique-user reach. Empty = any.

## `ages` (type: `array`):

Optional. Only ads targeting these age groups. Empty = any.

## `gender` (type: `string`):

Only ads targeting this gender.

## `sortBy` (type: `string`):

TikTok's sort order. For keyword searches TikTok mixes in relevance.

## `maxAdsPerSearch` (type: `integer`):

Stop each search term / URL after this many unique ads. TikTok serves 12 ads per page and shows at most about 5,000 per search.

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

Off (default) = fast card-only scrape: a few hundred ads per minute. On = also open each ad's detail record: landing page URL, call to action, objective, advertiser ID and country, "paid by", TikTok account (username, followers, verified), impressions per country with age x gender split, and targeting (countries, regions, ages, genders, audience size, interests, languages, devices). TikTok allows only one detail lookup per page load, so details run at roughly 25-35 ads per minute and are billed as a separate event.

## `maxConcurrency` (type: `integer`):

Parallel browser tabs (search terms and detail pages are spread over them). 3 fits the default 2 GB memory; for large detail runs use 4 GB memory and 4 tabs. Higher values from one IP can make TikTok block the IP (the Actor then switches to Apify Proxy automatically).

## `maxRetries` (type: `integer`):

Retries for each search page or detail record (with page reloads and, after repeated failures, a fresh browser session).

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

Not needed - the TikTok Ad Library is public. Without a proxy the Actor uses the server's own IP (fastest) and switches to Apify datacenter proxy sessions automatically if TikTok starts blocking detail pages. Set a proxy here only if you want every request to go through it.

## Actor input object example

```json
{
  "searchTerms": [
    "nike"
  ],
  "dateRange": "last-12-months",
  "adStatus": "all",
  "mediaType": "all",
  "gender": "all",
  "sortBy": "last-shown-desc",
  "maxAdsPerSearch": 50,
  "includeDetails": false,
  "maxConcurrency": 3,
  "maxRetries": 6,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `ads` (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 = {
    "searchTerms": [
        "nike"
    ],
    "maxAdsPerSearch": 50,
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("atlasdata/tiktok-ad-library-scraper-reliable").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 = {
    "searchTerms": ["nike"],
    "maxAdsPerSearch": 50,
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("atlasdata/tiktok-ad-library-scraper-reliable").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 '{
  "searchTerms": [
    "nike"
  ],
  "maxAdsPerSearch": 50,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call atlasdata/tiktok-ad-library-scraper-reliable --silent --output-dataset

```

## MCP server setup

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

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/5dSogMciyUGuGWECy/builds/dKEdOaufHYeygiPjg/openapi.json
