# TikTok Ad Library Scraper (`jmlp/tiktok-ad-library-scraper`) Actor

Scrape every ad an advertiser runs on TikTok from the official Commercial Content Library. Extract ad IDs, advertiser names, first & last shown dates, unique reach and direct downloadable video and image URLs. Search by advertiser or keyword across 32 countries. No login or API key.

- **URL**: https://apify.com/jmlp/tiktok-ad-library-scraper.md
- **Developed by:** [Mary Lou](https://apify.com/jmlp) (community)
- **Categories:**
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 2 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $0.15 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## TikTok Ad Library Scraper — competitor ads from the Commercial Content Library

Scrape every ad an advertiser runs on TikTok and export it as JSON, CSV or
Excel. Pulls ad text, advertiser, first and last shown dates, estimated
audience reach and — the part most tools cannot give you — a **direct,
downloadable link to the video or image creative**.

Data comes from TikTok's official **Commercial Content Library**, the ad
archive TikTok publishes under the EU Digital Services Act. No login, no API
key, no TikTok account.

***

### Read this first: what the archive covers

TikTok's Commercial Content Library exists to satisfy EU transparency law, so
it covers the **EU/EEA plus the UK and Switzerland — 32 countries**:

`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`

**There is no US data in it. None.** If you need US TikTok ads, this archive
does not contain them and no scraper can extract them from it. Use `all` in
*Countries* to sweep every covered country at once.

***

### What you can do with it

- **Competitor ad research** — see every creative a brand is running, and where
- **Creative intelligence and swipe files** — download the actual videos, not
  just metadata, and build a reference library
- **Ad creative trend analysis** — track how a brand's messaging and format mix
  shift over months
- **Track competitor campaign launches** — schedule weekly and diff the results
- **Influencer and branded-content discovery** — find which creators are
  running paid content for a brand
- **Market and category research** — search by keyword to see everyone
  advertising a theme
- **Compliance and brand-safety monitoring** — audit what is running under your
  name in each market

***

### How to scrape TikTok ads

**1.** Put a brand or advertiser into **Advertiser names**, e.g. `Nike`. Or use
**Keywords** to search ad *content* instead of advertiser names.

**2.** Pick your **Countries** — `GB`, a list, or `all` for every covered
market.

**3.** Leave **Proxy** on `RESIDENTIAL`. This one is genuinely not optional —
see below.

**4.** Press Start. **Max ads** is prefilled so a first run finishes in under a
minute.

***

### Input

| Field | What it does |
| --- | --- |
| **Advertiser names** | Brand or company names to look up |
| **Keywords** | Search the ad *content* rather than the advertiser |
| **Advertiser business IDs** | The most precise input — no name matching at all |
| **Countries** | 2-letter codes, or `all` for all 32 |
| **Last shown on/after, on/before** | Date window, filtered server-side on last-shown date |
| **Ad format** | All, video, image or text |
| **Ad status** | All, active only, or inactive only |
| **Sort by** | Last shown, published date, or unique reach — ascending or descending |
| **Targeted gender / age bands / reach bands** | Filter by the advertiser's own declared targeting |
| **Max ads** | Hard cap across every advertiser and country |

***

### Output

One row per ad. Export as **JSON, CSV, Excel, XML or RSS**.

```json
{
  "ad_id": "1855473544822801",
  "advertiser_name": "kakattekoiyo5",
  "advertiser_business_id": null,
  "title": "NIKExNIKE=??? #fyp #nike #ootd #fashion",
  "media_type": "video",
  "first_shown": "2026-01-27",
  "last_shown": "2026-01-27",
  "estimated_audience": "1K-10K",
  "media_url": "https://v58.tiktokcdn.com/video/tos/alisg/...",
  "media_url_proxied": "https://library.tiktok.com/api/v1/cdn/...",
  "cover_url": null,
  "media_count": 1,
  "scraped_region": "GB",
  "ad_library_url": "https://library.tiktok.com/ads/detail/?ad_id=1855473544822801"
}
```

| Field | Notes |
| --- | --- |
| `ad_id` | TikTok's stable id. Dedup on this |
| `advertiser_name` | The **account** that ran the ad — see below |
| `title` | The caption the ad ran with, hashtags included. On TikTok this is usually the whole ad copy |
| `media_url` | **Direct CDN link**, decoded from TikTok's proxy link so the asset is downloadable |
| `media_url_proxied` | The original library link, kept as a fallback |
| `estimated_audience` | Reach band, e.g. `0-1K`, `1K-10K` |
| `scraped_region` | Which country this row was collected for |
| `ad_library_url` | Opens the exact ad on TikTok, so any row can be verified by hand |

**The media URL is the differentiator.** TikTok serves creatives behind signed,
expiring proxy links. This decodes them back to the real CDN URL, so you get an
asset you can actually download rather than a link that dies.

***

### Two things worth knowing

**"Advertiser" means the account that ran the ad.** On TikTok most branded
content is posted by creators, so a search for `Nike` returns creator handles
alongside NIKE Retail B.V. That is the archive being accurate, not the scraper
being wrong — those creators really did run paid Nike content. Use **Advertiser
business IDs** when you want one specific company and nothing else.

**`spent` and `impression` are always empty.** TikTok publishes both fields and
fills in neither: 60 out of 60 ads measured came back with `0` and `""`. They
are passed through unchanged rather than quietly dropped, but do not build
anything on them. **`estimated_audience`** is the reach figure that is actually
populated.

***

### Why the proxy setting matters

TikTok's search endpoint **refuses datacenter IP addresses outright** while
still completing the session handshake on the same connection — so a run on the
wrong proxy group looks like a broken scraper rather than a blocked address.

Leave the proxy on **RESIDENTIAL** and it works. If a run reports refused jobs,
rerun with **Continue from last run**: a different residential exit is usually
all it takes, and nothing already collected is repeated.

***

### Pricing

**$0.15 per 1,000 ads, plus Apify platform usage** — the same price as the
other ad-library scrapers in this suite.

| Plan tier | Price per 1,000 ads |
| --- | --- |
| Free / Bronze | $0.17 |
| Silver, Gold, Platinum, Diamond | $0.15 |

Platform usage is billed by Apify at your own plan rate, separately from the
scraper charge. This Actor runs a real browser (TikTok's endpoint does not
answer anything else) and needs residential proxies, and both land in the usage
half — measured at **roughly $0.08 per 1,000 ads**, which stays small because
the browser is booted once per run and every search after that is a plain API
call.

The startup is a one-off ~10 seconds per run, so **fewer, larger runs are
cheaper than many small ones**. Set **Max ads** to cap any run exactly.

***

### FAQ

**Do I need a TikTok account or API key?**
No. This is a public archive with no login.

**Why only EU countries?**
Because that is all the library contains. It is an EU Digital Services Act
transparency obligation, not a global product.

**Is scraping the TikTok Ad Library legal?**
It reads a public registry TikTok publishes deliberately, under EU
transparency rules. No login, no paywall, and these are commercial
advertisements rather than personal data.

**Can I get every ad from one specific company?**
Yes. Put its **Advertiser business ID** in and nothing is matched by name. The
id appears in the output of any run that finds the company.

**Can I download the ad videos?**
Yes — that is what `media_url` is for. It is a direct CDN link, decoded from
TikTok's expiring proxy link.

**Why did a run return fewer ads than the total shown?**
Either **Max ads** capped it, or some jobs were refused. The run summary
records both, and refused jobs are resumable.

**How fast is it?**
Measured on the platform: 80 ads across 4 jobs (2 searches x 2 countries) in
27 seconds, 71 of them with a decoded direct media URL.

**Can I run it on a schedule?**
Yes. Run weekly and diff the results to track how a brand's creative mix
changes over time.

***

### Related scrapers

- **Meta Ads Library Scraper** — Facebook and Instagram ads, same suite, same
  $0.15 per 1,000
- **Google Ads Transparency Center Scraper** — Search, YouTube, Display,
  Shopping and Maps ads
- **Google Search Scraper** — organic SERP results and rank tracking
- **Website Contact Scraper** — emails, phones and socials for any domain list

# Actor input Schema

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

Advertiser names to look up, e.g. 'Nike' or 'Booking.com'. Matches the advertiser as registered with TikTok, which is usually the legal entity ('NIKE Retail B.V.') rather than the brand, and partial names work. Each name is searched in every region below.

## `searchTerm` (type: `string`):

A single advertiser name, for convenience when you only have one.

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

Search the ad CONTENT rather than the advertiser name, e.g. 'running shoes' or 'black friday'. Use this to find every advertiser talking about a theme, instead of every ad from one company.

## `advertiserBusinessIds` (type: `array`):

TikTok business ids, if you already have them. The most precise input: nothing has to be matched by name, so nothing can match the wrong company.

## `regions` (type: `array`):

2-letter country codes, e.g. GB, DE, FR. TikTok's Commercial Content Library exists to satisfy the EU Digital Services Act, so it covers the EU/EEA plus the UK and Switzerland ONLY - there is no US, Canadian or APAC data in it. Every country is a separate job, which is also how this actor parallelises. Use 'all' to cover all 32.

## `region` (type: `string`):

A single 2-letter country code, used alongside or instead of the list above.

## `minDate` (type: `string`):

YYYY-MM-DD. TikTok's window filters on the date an ad was LAST shown, server-side. Leave both dates empty to cover roughly the last three years, which is everything the library holds - note the website itself defaults to 30 days and quietly hides the rest.

## `maxDate` (type: `string`):

YYYY-MM-DD. Combined with the field above it selects ads whose last-shown date falls in the window.

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

REQUIRED, and it must be RESIDENTIAL. Measured: TikTok's search endpoint refuses every datacenter address tried - including Apify's own platform IP and both datacenter pools - while still completing the handshake, so a datacenter run looks like a broken session rather than a blocked IP.

## `maxAds` (type: `integer`):

Stop after this many unique ads, counted across every advertiser and country. Caps runtime and cost. Prefilled so a first run finishes in well under a minute - clear it to scrape everything.

## `maxPages` (type: `integer`):

Safety cap while testing, applied to each country/search job separately. One page is up to 100 ads. Empty = all.

## `adType` (type: `string`):

Limit to one creative format.

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

Active = running right now. Inactive = stopped. All = the advertiser's whole history in this country.

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

The order TikTok returns ads in. Only affects which ads you get first when you set a Max ads limit.

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

Keep only ads targeted at this gender, as declared in the advertiser's own targeting.

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

Keep only ads targeted at these age bands. Values are exactly: all, 13,17 / 18,24 / 25,34 / 35,44 / 45,54 / 55,100. Leave empty for all.

## `adReach` (type: `array`):

Keep only ads in these reach bands. Values are exactly: all, 0-10K, 10K-100K, 100K+. Leave empty for all.

## `pageSize` (type: `integer`):

Ads per request. 12 is what the website itself uses and the value this was measured against; larger values are accepted by the schema but unverified.

## `delayMs` (type: `integer`):

Milliseconds to sleep between result pages. TikTok throttles hard, and a refused exit has to be replaced with a whole new browser session, so this is worth keeping generous.

## `raw` (type: `boolean`):

Push TikTok's records untouched, without the flattening, date conversion and CDN-URL decoding of the fixed output schema.

## `proxyRotations` (type: `integer`):

If a job is throttled, the actor mints a new proxy session AND a new session token, then retries this many times.

## `resume` (type: `boolean`):

Save progress every ~30s so a run that gets migrated or restarted by the platform picks up where it stopped, without duplicating anything. No effect on fresh runs.

## `continueFromLastRun` (type: `boolean`):

If your previous run with the same input was interrupted, throttled, or stopped because it hit Max ads, scrape only what it missed. Note the earlier ads stay in THAT run's dataset, so this run's dataset contains only the remainder.

## Actor input object example

```json
{
  "searchTerms": [
    "Nike"
  ],
  "regions": [
    "GB"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "maxAds": 500,
  "adType": "all",
  "adStatus": "all",
  "sortBy": "last_shown_date,desc",
  "gender": "ALL",
  "pageSize": 12,
  "delayMs": 500,
  "raw": false,
  "proxyRotations": 3,
  "resume": true,
  "continueFromLastRun": false
}
```

# Actor output Schema

## `ads` (type: `string`):

One record per unique ad: the advertiser, the format, when it first and last ran, how many people saw it, and a direct downloadable link to the video or image.

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

How many ads were collected versus how many TikTok reported, which searches and countries were covered, the breakdown by media type, and whether anything was cut short by a rate limit or the run's maximum cost.

# 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"
    ],
    "regions": [
        "GB"
    ],
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ]
    },
    "maxAds": 500
};

// Run the Actor and wait for it to finish
const run = await client.actor("jmlp/tiktok-ad-library-scraper").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"],
    "regions": ["GB"],
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
    },
    "maxAds": 500,
}

# Run the Actor and wait for it to finish
run = client.actor("jmlp/tiktok-ad-library-scraper").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"
  ],
  "regions": [
    "GB"
  ],
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ]
  },
  "maxAds": 500
}' |
apify call jmlp/tiktok-ad-library-scraper --silent --output-dataset

```

## MCP server setup

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

```

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/usSSdcSnSZbfNz4EB/builds/LFBgd9uCgi0qLu1yc/openapi.json
