# TikTok Ads Bulk Scraper — Creative Center Top Ads (`titan_coder/tiktok-ads-bulk-scraper`) Actor

Bulk TikTok ad exports charge you per row returned — including the duplicates you get when you sweep several countries, and the overlap is real (38% between US and GB). This one de-duplicates before it bills, so you pay for distinct creatives, not repeated rows.

- **URL**: https://apify.com/titan\_coder/tiktok-ads-bulk-scraper.md
- **Developed by:** [Radu Furtuna](https://apify.com/titan_coder) (community)
- **Categories:** Social media, Marketing
- **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?

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 Ads Bulk Scraper — Creative Center Top Ads

Bulk-export the top-performing ads from **TikTok Creative Center** — video URLs, brand names, CTR, cost,
likes, industry and objective — across as many country and time-window slices as you want, de-duplicated
into one clean dataset.

### How it works

1. **No third-party account needed.** TikTok Creative Center renders client-side and refuses requests
   made outside a real browser session, so a real anti-detect browser is required — this Actor uses a
   built-in stealth browser by default. You can still point it at your own remote browser (`cdpUrl`)
   if you prefer; the backend is never switched automatically and is reported in the run's coverage.
   This Actor only reads what the public board loads for an ordinary visitor.
2. You pick **countries** and **periods**. Every `country × period` pair is one slice of the board.
3. Each slice is swept to its full depth — up to **100 ads** (5 pages × 20), which is the source's own
   hard ceiling per slice.
4. Ads are **de-duplicated across slices** before they reach the dataset, and you are charged once per
   distinct ad. Slices overlap in reality (measured: US/30d and GB/30d share 38 of ~100), so this matters.

### Input

```json
{
  "countryCodes": ["US", "GB", "DE"],
  "periods": [7, 30],
  "orderBy": "ctr",
  "maxPagesPerSlice": 5,
  "maxAds": 1000
}
```

| Field | Meaning |
|---|---|
| `cdpUrl` | Optional. Leave empty to use the built-in stealth browser. If set, stored as a secret and never logged. |
| `countryCodes` | Two-letter codes, up to 28 (the source's own list). Default `["US"]`. |
| `periods` | `7`, `30`, `180`. Each period is a separate slice. Default `[30]`. |
| `orderBy` | `ctr` (default) and `impression` are deterministic; `for_you` is personalised and rotates. |
| `maxPagesPerSlice` | `1`–`5`. Default `5` (the full 100 ads per slice). |
| `maxAds` | Hard stop, counted **after** de-duplication — the number of distinct ads you actually receive. |
| `webhookUrl` | Optional HTTPS endpoint receiving a JSON run summary. |

### Output

One dataset row per distinct ad:

`adId`, `title`, `brand`, `ctr`, `cost`, `likes`, `industryKey`, `objectiveKey`, `videoId`, `durationSec`,
`coverUrl`, `adUrl` (the ad's card in TikTok Creative Center — a link that actually opens; the board exposes an internal video id, not a public post id, so no `tiktok.com/@/video/...` link is offered), `countryCode`, `period`, `orderBy`, `runId`, `scrapedAt`.

`countryCode` / `period` / `orderBy` record which slice the ad was first seen in, so the export stays
traceable. The key-value store also holds `coverage` with per-slice detail: pages seen, source
`total_count`, and whether the window was truncated or the pagination incomplete.

### Billing

- **`run-started`** — once per run, charged **only after the board has actually returned a valid,
  recognised response**. A blocked or dead run charges nothing at all, not even the start event.
- **`ad-scraped`** — once per **distinct** ad, charged only **after** the row is written to the dataset.

You are never charged twice for the same creative within a run, and never for a run that returned nothing.

#### How this compares

`run-started` **$0.002** · `ad-scraped` **$0.0009** ($0.90 per 1,000 distinct ads).

The list price is mid-range for the category. What changes the actual bill is de-duplication: slices of the
board overlap heavily, and this actor charges per **distinct** creative, not per row returned. Measured
overlap between the US and GB 30-day slices was 38%.

So for a realistic multi-country sweep — say 3 countries × 30 days, 300 rows fetched, ~190 of them
distinct after overlap is removed:

| | what you pay for | bill for that sweep |
|---|---|---|
| Bulk scraper at $3.00 / 1,000 | every row, duplicates included | $0.90 |
| Bulk scraper at $1.50 / 1,000 | every row, duplicates included | $0.45 |
| **TikTok Ads Bulk Scraper** at $0.90 / 1,000 | **distinct ads only** | **$0.17** |
| Bulk scraper at $0.49 / 1,000 | every row, duplicates included | $0.15 |
| Cheapest bulk dumper at $0.01 / 1,000 | every row, duplicates included | $0.003 |

Being straight about it: on sticker price this actor is not the cheapest on the Store, and one dumper is
cheaper than anything short of free. On the bill for a real multi-country sweep it lands next to the
cheapest mainstream option — while giving you a clean, de-duplicated dataset instead of repeated rows you
have to sort out yourself. The wider you sweep, the more that gap works in your favour.

### Limits, honestly

- **100 ads per slice** is the source's hard ceiling (5 pages × 20). Volume comes from sweeping more
  slices, not from asking for a bigger page.
- **Slices overlap.** 28 countries × 3 periods is 84 slices and a theoretical 8,400 rows, but the distinct
  total is lower — measured overlap between US/30d and GB/30d was 38%.
- **No industry / objective / language filter.** Creative Center exposes those controls, but the board
  ignores them when they are passed in the page URL — verified directly: requesting a specific industry
  returned the same industry mix as an unfiltered request. Rather than ship a filter that silently does
  nothing, this actor does not offer one. Every ad's own `industryKey` and `objectiveKey` are in the
  output, so you can filter after export.
- **`for_you` rotates.** If you choose it, two runs will not return the same set. `ctr` and `impression`
  are deterministic — prefer them for repeatable exports.
- TikTok does not publish an ad's real first-appearance date, and this actor does not claim one.

***

Author: **OmniCoder** (https://t.me/OmniCoder)

# Actor input Schema

## `cdpUrl` (type: `string`):

Optional. Leave EMPTY to use the built-in stealth anti-detect browser — no third-party account or key needed. Provide your own remote browser endpoint (ws:// or wss://, e.g. a Bright Data Scraping Browser) only if you prefer to route through your own. The backend is never switched automatically and is reported in the run's coverage.

## `countryCodes` (type: `array`):

Two-letter country codes to sweep, e.g. US, GB, DE. Each country is a separate slice of the board. Up to 28 (the source's own list).

## `periods` (type: `array`):

Which time windows to sweep: 7, 30 and/or 180. Each period is a separate slice, so \[7,30] doubles the volume.

## `orderBy` (type: `string`):

How the board ranks ads. CTR and Reach are deterministic and repeatable; For You is personalised and its window rotates between runs.

## `maxPagesPerSlice` (type: `integer`):

1-5. The source hard-caps each slice at 100 ads (5 pages x 20), so 5 is the maximum useful value. The prefilled values are a deliberately cheap first run (one country, one period, one page) so you can see the shape of the data before you widen the sweep.

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

Hard stop for the run. Counted after de-duplication, so it is the number of distinct ads you actually receive and pay for.

## `webhookUrl` (type: `string`):

Optional. Receives a JSON summary of the run. HTTPS only.

## Actor input object example

```json
{
  "countryCodes": [
    "US"
  ],
  "periods": [
    30
  ],
  "orderBy": "ctr",
  "maxPagesPerSlice": 1,
  "maxAds": 20
}
```

# Actor output Schema

## `results` (type: `string`):

Every row this run produced. Key fields: adId, brand, title, ctr, likes, countryCode, period, adUrl.

## `coverage` (type: `string`):

What this run actually covered and what it charged for: per-target status and reason, rows delivered and rows billed. Enough to reconcile every charge against every row.

# 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 = {
    "countryCodes": [
        "US"
    ],
    "periods": [
        30
    ],
    "maxPagesPerSlice": 1,
    "maxAds": 20
};

// Run the Actor and wait for it to finish
const run = await client.actor("titan_coder/tiktok-ads-bulk-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 = {
    "countryCodes": ["US"],
    "periods": [30],
    "maxPagesPerSlice": 1,
    "maxAds": 20,
}

# Run the Actor and wait for it to finish
run = client.actor("titan_coder/tiktok-ads-bulk-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 '{
  "countryCodes": [
    "US"
  ],
  "periods": [
    30
  ],
  "maxPagesPerSlice": 1,
  "maxAds": 20
}' |
apify call titan_coder/tiktok-ads-bulk-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,titan_coder/tiktok-ads-bulk-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/vU4VYCfoildm1BtPg/builds/joOImOQQJg0HdFVvx/openapi.json
