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

Search TikTok's official DSA Commercial Content Library by advertiser name or keyword and export ad-creative rows — caption, video/cover URLs, shown dates, estimated audience band, and optional landing-page/CTA enrichment. EU/EEA + UK + Switzerland + Turkey coverage only.

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

## Pricing

Pay per event

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

<div align="center">
  <img src=".actor/icon.svg" width="160" alt="Devil Scrapes mark" />

## TikTok Ad Library Scraper

**💰 $3.20 / 1 000 results**  ·  pay only for results  ·  no credit card to try

*The devil's in the data — we chase every ad down TikTok's cursor.* 😈

Search TikTok's official DSA-mandated Commercial Content Library (`library.tiktok.com`) by advertiser name or keyword and export ad-creative rows — caption, video/cover URLs, shown dates, estimated audience band. EU/EEA + UK + Switzerland + Turkey coverage only.

</div>

***

### 🎯 What this scrapes

TikTok publishes a free, keyless **Commercial Content Library** to
satisfy the EU's Digital Services Act ad-transparency mandate. This
Actor searches it by advertiser name or keyword and returns one
structured row per matching ad — advertiser, caption, first/last shown
dates, estimated audience band, and creative URLs. It completes the
ad-transparency class alongside our `google-ads-transparency`,
`linkedin-ad-library-scraper`, and `meta-ad-library-scraper` Actors.

### 🔥 Features

- 🛡️ **We open a real browser session to earn your search a seat**, then reuse that trust (Firefox-fingerprint requests) for the rest of the run — never raw Python knocking on the door.
- 🔁 **We retry with exponential backoff** on `429 / 503` and network errors, honouring `Retry-After` — up to 5 attempts per call.
- 🧭 **We follow TikTok's cursor pagination exactly** — the opaque `search_id` token from each response is echoed verbatim into the next request, never a guessed offset.
- 🌐 **Apify Proxy on every request**, ready to escalate to residential if TikTok ever tightens up (it hasn't yet).
- 🧱 **We isolate per-row failures** — one malformed ad object is skipped and logged, the run keeps going.
- 💰 **You pay only for ads that land.** A search that matches nothing still succeeds — you're never charged for a failed run.

### 💡 Use cases

- **Competitive intel** — track a competitor's TikTok ad creative, captions, and rotation cadence over time.
- **Paid-media benchmarking** — compare estimated audience bands and creative formats across advertisers in your category.
- **Brand protection** — check whether your brand name or trademark shows up in someone else's TikTok ads.
- **Creative research** — pull real, live ad captions and hooks for inspiration before building your own campaign.
- **Compliance / ad-transparency reporting** — export a clean, structured record of an advertiser's EU-region TikTok activity.

### ⚙️ How to use it

1. Click **Try for free** at the top of the page.
2. Enter one or more **Queries** — advertiser name(s) or keyword(s), e.g. `["nike", "zara"]`.
3. Optionally set **Region** (defaults to `"all"` — every EU/EEA + UK + Switzerland + Turkey region TikTok supports).
4. Optionally set **Date from** / **Date to** (defaults to a trailing 30-day window).
5. Set **Max results per query**.
6. Click **Start**. Rows stream into the run's dataset as TikTok's cursor pages are followed, one independent loop per query.
7. Export from **Storage → Dataset** as JSON, CSV, or Excel — or fetch via the API.

### 📥 Input

| Field | Type | Required | Default | Notes |
|---|---|:--:|---|---|
| `queries` | `array<string>` | yes | — | Advertiser name(s) / keyword(s). One search per entry (1-200 chars each). |
| `region` | `string` | no | `"all"` | `"all"` or a live TikTok support-region code (EU/EEA + UK + Switzerland + Turkey only — no US). |
| `dateFrom` | `string` | no | 30 days before `dateTo` | ISO `YYYY-MM-DD`. |
| `dateTo` | `string` | no | today | ISO `YYYY-MM-DD`. |
| `maxResultsPerQuery` | `integer` | no | `500` | Cap per query across cursor pages (1-20,000). |
| `proxyConfiguration` | `object` | no | `{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "US"}` | Apify Proxy configuration. |

#### Example input

```json
{
  "queries": ["nike", "zara"],
  "region": "all",
  "maxResultsPerQuery": 25,
  "proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"], "apifyProxyCountry": "US" }
}
```

### 📤 Output

Every row is one matched ad.

| Field | Type | Notes |
|---|---|---|
| `ad_id` | `string` | TikTok's internal ad ID. |
| `query_term` | `string` | The query that produced this row. |
| `region` | `string` | The region scope that produced this row. |
| `advertiser_name` | `string` | Advertiser display name — the only advertiser identifier TikTok exposes (no numeric advertiser ID). |
| `ad_caption` | `string \| null` | Ad caption text. |
| `first_shown_date` / `last_shown_date` | `string \| null` | ISO-8601 UTC. |
| `estimated_audience` | `string \| null` | Estimated audience band, e.g. `"100K-200K"`. |
| `spend` | `string \| null` | Reported spend band; empty/null for most ordinary (non-DSA-flagged) ads — never fabricated. |
| `impressions` | `integer \| null` | Reported impression count; `0` for most ordinary ads, `null` only if genuinely missing. |
| `video_url` / `cover_img` | `string \| null` | Primary creative video/cover-image URL. |
| `image_urls` | `array<string>` | Additional creative image URLs, if any. |
| `ad_url` | `string` | Direct TikTok Ad Library URL for this ad. |
| `scraped_at` | `string` | ISO-8601 UTC timestamp this row was scraped. |

#### Example output

```json
{
  "ad_id": "1873877803725954",
  "query_term": "nike",
  "region": "all",
  "advertiser_name": "NIKE Retail B.V.",
  "ad_caption": "Une rentrée signée Nike",
  "first_shown_date": "2026-08-16T00:00:00+00:00",
  "last_shown_date": "2026-09-05T00:00:00+00:00",
  "estimated_audience": "100K-200K",
  "spend": null,
  "impressions": 0,
  "video_url": "https://library.tiktok.com/api/v1/cdn/.../video/...",
  "cover_img": "https://p16-common-sign.tiktokcdn.com/...",
  "image_urls": [],
  "ad_url": "https://library.tiktok.com/ads/detail/?ad_id=1873877803725954",
  "scraped_at": "2026-09-12T12:00:00+00:00"
}
```

### 💰 Pricing

Pay-Per-Event — you pay only when these events fire:

| Event | USD | What it is |
|---|---:|---|
| `actor-start` | $0.20 | One-off warm-up charge — fires on the **first delivered row**, not at boot |
| `ad-result` | $0.003 | Per ad row written to the dataset |

Example: 1,000 matched ads ≈ **$3.20** total ($0.20 start + 1000 x
$0.003). No subscription, no minimum, no card to start — Apify gives
every new account free credit.

**A run that delivers nothing costs you nothing.** 🧾 The start fee is charged
on the first row that actually reaches your dataset, so if TikTok blocks the
run or your search matches no ads, your bill for it is $0.00.

**Keep the `RESIDENTIAL` proxy default (with its US country pin) — both are load-bearing.** 🛡️
This Actor mints its API token by loading the Ad Library the way a browser does, once per run.
From a datacenter exit that page simply never issues the request the token rides on, so the run
stops with a clear error instead of quietly returning nothing — we verified both sides in the
cloud on 2026-09-12. The proxy exit is also pinned to the US to match the browser session's
locale — a mismatched exit country is a fingerprint red flag that can stall the token mint. You
don't need to think about any of this; just don't override the proxy defaults to save a little
cost, because the run will fail rather than degrade.

### 🚧 Limitations

TikTok's Commercial Content Library covers \*\*EU/EEA + UK + Switzerland

- Turkey only\*\* — there is no US region, and this is a hard product
  limit, not something this Actor can work around. `region` is validated
  live against TikTok's own support-regions list at the start of every
  run. The `total` count TikTok's search response returns is a **display
  cap** (commonly `5000`), not an exact match count — treat it as
  "5,000+", not precise. `spend`/`impressions` are genuinely
  empty/`0` for ordinary commercial ads (TikTok only populates these for
  DSA-flagged categories like political/social-issue ads) — this Actor
  ships them as-is rather than inventing numbers. There is no numeric
  advertiser ID in either the list or detail response — only the
  advertiser's display name.

### ❓ FAQ

**Does this need a TikTok account or API key?**

No. TikTok's Commercial Content Library search API is free and keyless.

**Why is there no US region?**

TikTok built this product to satisfy the EU's DSA transparency mandate
— it genuinely only covers EU/EEA + UK + Switzerland + Turkey. This
Actor validates your `region` input against TikTok's own live
support-regions list and fails loud (before any search call) if you
pick one outside it.

**What happens if my query matches nothing?**

That query finishes successfully with zero rows and a status message
naming it — you're not charged per-result for a failed search, and
other queries in the same run are unaffected.

**Can I search multiple advertisers in one run?**

Yes — pass multiple `queries`. Each runs its own independent
cursor-pagination loop, so one query hitting a snag never stops the
others from finishing.

### 💬 Your feedback

Spotted a bug, hit a weird edge case, or need a field we don't ship
yet? Open an issue on the Actor's **Issues** tab on Apify Console — we
ship fixes weekly and we read every report.

***

<div align="center">

Built by **[Devil Scrapes](https://apify.com/DevilScrapes)** 😈 — a small fleet of
opinionated public-data Actors. Honest pricing, real engineering, zero fine print.

</div>

# Changelog

This Actor's version history is a separate document: https://apify.com/devilscrapes/tiktok-ad-library-scraper/changelog.md

# Actor input Schema

## `queries` (type: `array`):

Advertiser name(s) or keyword(s) to search. One search is run per entry.

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

DSA region scope: "all" or a code from TikTok's live support-regions list (EU/EEA + UK + Switzerland + Turkey only — no US region exists).

## `dateFrom` (type: `string`):

Only return ads first shown on or after this date (ISO-8601, YYYY-MM-DD). Defaults to 30 days before Date to when omitted.

## `dateTo` (type: `string`):

Only return ads shown on or before this date (ISO-8601, YYYY-MM-DD). Defaults to today when omitted.

## `maxResultsPerQuery` (type: `integer`):

Cap on ad rows collected per query, across cursor-paginated pages.

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

Apify Proxy routing. RESIDENTIAL is the default and you should keep it: from an Apify datacenter exit the Ad Library page never issues the /api/v1/search request the token bootstrap depends on, so the run fails loud — verified in the cloud 2026-09-12. The exit country is pinned to US to match the fixed en-US browser locale the Camoufox token mint launches with (T26) — a geo-random residential exit paired with a static locale is a fingerprint mismatch that can park the mint on an interstitial indefinitely.

## Actor input object example

```json
{
  "queries": [
    "nike",
    "zara"
  ],
  "region": "all",
  "maxResultsPerQuery": 25,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}
```

# Actor output Schema

## `datasetItems` (type: `string`):

All dataset items as JSON.

## `datasetItemsCsv` (type: `string`):

Same data exported to CSV.

## `datasetView` (type: `string`):

Open the run dataset in the Console.

# 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 = {
    "queries": [
        "nike",
        "zara"
    ],
    "region": "all",
    "maxResultsPerQuery": 25,
    "proxyConfiguration": {
        "useApifyProxy": true,
        "apifyProxyGroups": [
            "RESIDENTIAL"
        ],
        "apifyProxyCountry": "US"
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("devilscrapes/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 = {
    "queries": [
        "nike",
        "zara",
    ],
    "region": "all",
    "maxResultsPerQuery": 25,
    "proxyConfiguration": {
        "useApifyProxy": True,
        "apifyProxyGroups": ["RESIDENTIAL"],
        "apifyProxyCountry": "US",
    },
}

# Run the Actor and wait for it to finish
run = client.actor("devilscrapes/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 '{
  "queries": [
    "nike",
    "zara"
  ],
  "region": "all",
  "maxResultsPerQuery": 25,
  "proxyConfiguration": {
    "useApifyProxy": true,
    "apifyProxyGroups": [
      "RESIDENTIAL"
    ],
    "apifyProxyCountry": "US"
  }
}' |
apify call devilscrapes/tiktok-ad-library-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,devilscrapes/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/QFM7HDyDhRz4t4ggk/builds/iCsbvARlSBHoPFhq5/openapi.json
