# TikTok Ad Library Scraper — Resume & $0-on-Block (`domestic_buffalograss/tiktok-ad-library-reliable`) Actor

Get TikTok ads from the EU Commercial Content Library (DSA archive, searchable by advertiser) or Creative Center Top Ads (global high-performers with CTR/like metrics). Resumable; never charges for blocked or rate-limited runs.

- **URL**: https://apify.com/domestic\_buffalograss/tiktok-ad-library-reliable.md
- **Developed by:** [Sumitsubo](https://apify.com/domestic_buffalograss) (community)
- **Categories:** Marketing, Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 ad scraped (1 result)s

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?

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 — EU Library & Top Ads, Resume & $0-on-Block

Fetch ads from **both** TikTok transparency surfaces in one Actor — built for **large collections that never silently lose data** and never charge you for blocked runs.

- **EU Commercial Content Library** (`library.tiktok.com`) — *every* ad delivered to the EU (DSA archive, ~12 months), searchable by advertiser/keyword, with advertiser legal names, first/last shown dates and **audience estimates** (e.g. `10K-100K` unique users).
- **Creative Center Top Ads** (`ads.tiktok.com/business/creativecenter`) — a browse feed of global high-performing ads with **CTR, likes and video duration**, plus direct video URLs. This surface has no keyword search on the login-free tier and returns roughly the top 20 ads per country/period — an honest sample, not an archive.

### Why this one

- **Resumable pagination** — kill or time out a large run and it continues exactly where it stopped. No re-fetching, no double charges. (Built because users of other TikTok ads scrapers asked for exactly this.)
- **$0 when blocked** — a blocked/rate-limited fetch is detected and **not billed**. TikTok's search API has a quirk where a bad request returns an *empty-but-OK* response; this Actor cross-checks against the visible UI total so a soft-fail is never sold to you as "no ads found".
- **No silent loss** — every work unit is checkpointed; failures land in the dataset as `{_error, _reason}` records, never dropped.
- **Real media files** — optionally download creative videos/covers to key-value storage (TikTok CDN hosts only, per-ad cap).
- **Clean, stable schema** — one flat `snake_case` row per ad across both surfaces, null-safe, with `_meta` versioning.

### What it does NOT do (honest limits)

- The EU library only contains ads **delivered to the EU** (that's the legal scope of the archive). Global coverage beyond the EU comes from the Creative Center surface, which is a *curated sample of high-performing ads*, not an exhaustive archive.
- TikTok does not expose exact spend/impressions for commercial ads. `audience_estimate` is TikTok's own published range; this Actor does not invent numbers.
- Login-gated data is out of scope by design (logged-out public surfaces only).

### Input

| Field | Meaning |
|---|---|
| `queries` | Advertiser names or keywords (required; used by the CCL surface — Creative Center is a browse-only feed) |
| `surfaces` | `["ccl"]` (EU library, default) and/or `["cc"]` (Creative Center Top Ads, one work unit per country) |
| `countries` | CCL: target country ISO or `all`. CC: ISO (no `all`; falls back to US) |
| `lookbackDays` | CCL last-shown window, max 365 |
| `mode` | `snapshot` or `snapshot_with_media` (also downloads creatives) |
| `maxItems` / `maxBudgetUsd` | Hard caps — you are never charged beyond them (persist across resumes) |
| `maxAdsPerUnit` | Cap of ads fetched per (surface × query × country) work unit (advanced, default 600) |
| `ccPeriodDays` | Creative Center Top Ads ranking window in days — 7, 30, or 180 (default 30) |
| `useResidentialProxy` | Off by default (TikTok libraries are reachable from datacenter egress) |

### Output (one row per ad)

```json
{
  "ad_id": "1873877803725954",
  "country": "DE",
  "surface": "commercial_content_library",
  "advertiser_name": "NIKE Retail B.V.",
  "title": "Une rentrée signée Nike",
  "first_shown": "2026-08-20", "last_shown": "2026-08-31", "days_shown": 11,
  "audience_estimate": "10K-100K",
  "metrics": { "impression": 0 },
  "industry": null, "objective": null, "audit_status": "1",
  "detail_url": "https://library.tiktok.com/ads/detail/?ad_id=1873877803725954",
  "media": [{ "kind": "video", "url": "...", "stored_key": "media_video_ab12.mp4" }],
  "image_urls": ["https://p19-...tiktokcdn.com/..."],
  "video_urls": ["https://library.tiktok.com/api/v1/cdn/..."],
  "_meta": { "_scraped_at": "2026-09-02T00:00:00Z", "_source_url": "...", "_actor_version": "<actor build version>", "_schema_version": "<schema version>" }
}
```

Creative Center rows carry `metrics: { ctr, like, video_duration }` (plus `cost_tier`, which TikTok only fills for roughly half of ads and whose scale it does not publish) and `industry`/`objective` keys instead of dates.

### Pricing (pay-per-event)

| Event | Price |
|---|---|
| `actor-start` | $0.02 |
| `ad-scraped` | $0.001 per ad |
| `media-downloaded` | $0.005 per file (only in `snapshot_with_media`) |

You are **not** charged for blocked runs, empty results, or ads beyond `maxItems` / `maxBudgetUsd`.

### How it works (and why it keeps working)

The Actor drives a real headless browser over the **public, logged-out** pages and collects the JSON responses the page itself requests — no signature reverse-engineering, no login, no CAPTCHA solving. When TikTok rotates its request-signing internals, this Actor keeps working because the page's own JavaScript does the signing. UI selectors are isolated in one place (`src/platform/browserCollector.js`) for quick fixes if the layout changes.

Reliability core (billing safety, resume, block/empty classification) is the same adversarially-reviewed engine used by our Meta Ad Library and Google Ads Transparency actors: mid-run kill → no double charge & no data loss; blocked/rate-limited/empty → $0; push failure → not billed; integer micro-dollar accounting. `npm test` runs the full suite (incl. a real-package API-surface check) against **real captured API responses** with no network access.

# Actor input Schema

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

Advertiser names or keywords to search for (used by the EU library surface; Creative Center Top Ads is a browse-only feed without keyword search).

## `surfaces` (type: `array`):

Where to fetch ads from. 'ccl' = EU Commercial Content Library (all EU-delivered ads, DSA archive, searchable, includes audience estimates). 'cc' = Creative Center Top Ads (browse feed of ~top 20 high-performing ads per country with CTR/like metrics; no keyword filter).

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

For CCL: ISO code of the ad target country, or 'all' for all countries. For Creative Center: ISO code ('all' falls back to US).

## `lookbackDays` (type: `integer`):

How far back to search by last-shown date (CCL surface). Max 365 (DSA archive keeps ~12 months).

## `mode` (type: `string`):

'snapshot' fetches ad records only. 'snapshot\_with\_media' also downloads creative files to key-value storage.

## `maxItems` (type: `integer`):

Hard cap on billable ads. You are never charged for more than this many ads across the whole run (including resumes).

## `maxAdsPerUnit` (type: `integer`):

Cap of ads fetched per (surface × query × country) work unit.

## `ccPeriodDays` (type: `integer`):

Top Ads ranking window for the Creative Center surface (7/30/180).

## `maxBudgetUsd` (type: `number`):

Optional hard USD ceiling. The run stops before exceeding it, and the ceiling persists across resumes. Leave empty for no cap; 0 means charge nothing.

## `useResidentialProxy` (type: `boolean`):

Off by default (TikTok libraries are reachable from datacenter egress). Turn on only if your runs get blocked.

## Actor input object example

```json
{
  "queries": [
    "nike",
    "adidas"
  ],
  "surfaces": [
    "ccl"
  ],
  "countries": [
    "all"
  ],
  "lookbackDays": 365,
  "mode": "snapshot",
  "maxItems": 500,
  "maxAdsPerUnit": 600,
  "ccPeriodDays": 30,
  "useResidentialProxy": false
}
```

# Actor output Schema

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

Every ad this run collected, one row per ad across both surfaces (EU Commercial Content Library / Creative Center Top Ads). Never charged for blocked/failed work units.

## `archivedMedia` (type: `string`):

Creative videos/covers downloaded to the key-value store. Present only when mode is snapshot\_with\_media.

## `runSummary` (type: `string`):

What the run did: how many ads were fetched per surface, blocked/empty units, and whether billing was active and matched the code's expected prices.

# 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"
    ],
    "surfaces": [
        "ccl"
    ],
    "countries": [
        "all"
    ],
    "mode": "snapshot"
};

// Run the Actor and wait for it to finish
const run = await client.actor("domestic_buffalograss/tiktok-ad-library-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 = {
    "queries": ["nike"],
    "surfaces": ["ccl"],
    "countries": ["all"],
    "mode": "snapshot",
}

# Run the Actor and wait for it to finish
run = client.actor("domestic_buffalograss/tiktok-ad-library-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 '{
  "queries": [
    "nike"
  ],
  "surfaces": [
    "ccl"
  ],
  "countries": [
    "all"
  ],
  "mode": "snapshot"
}' |
apify call domestic_buffalograss/tiktok-ad-library-reliable --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,domestic_buffalograss/tiktok-ad-library-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/UWbBssGEQGXuRQQ3n/builds/31mhP8Xl93enZJJwb/openapi.json
