# TikTok Ads Scraper - Ad Library at $0.45/1K (`dami_studio/tiktok-ads-scraper`) Actor

Reads TikTok's public Commercial Content Library by keyword, advertiser, country and date. One row per ad: advertiser, caption, first and last shown dates, countries targeted, unique-users band, video, thumbnail, CTA and landing URL. EEA and UK only - no US edition.

- **URL**: https://apify.com/dami\_studio/tiktok-ads-scraper.md
- **Developed by:** [Dami's Studio](https://apify.com/dami_studio) (community)
- **Categories:** Social media, Lead generation
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.45 / 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?

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 Scraper — Commercial Content Library

TikTok publishes an ad archive at `library.tiktok.com/ads` because EU law makes it. This reads that
archive by keyword, advertiser, country and date, and gives you one flat row per ad.

Each row has the advertiser, the ad caption, the first and last dates it ran, every country it was
shown in, the unique-users band TikTok publishes for it, the video and thumbnail, the call-to-action
button text and the landing page the ad clicks through to.

Read the coverage note before you plan anything around this. The archive only exists because of EU
and UK transparency rules, so it only covers the EEA plus the UK, Switzerland, Norway, Iceland and
Liechtenstein. There is no US edition. Nothing before 2022-10-01 is in it either.

No account and no browser. The library hands a guest token to anyone who asks for one, and that's
what the run uses.

### Price

$0.45 per 1,000 ads, plus a $0.0015 start fee per run.

| Ads | Total |
|---|---|
| 100 | $0.0465 |
| 1,000 | $0.4515 |
| 10,000 | $4.5015 |

One `ad-scraped` event per ad row. Nothing else is metered per row. The sample row and every
diagnostic row are free and carry `"charged": false`. Ads already seen earlier in the same run are
dropped before they're charged, so a creative matching two of your search terms costs you once. A
country code or date the library doesn't support comes back as a free diagnostic row rather than a
billed one.

### Input

```json
{
  "searchTerms": ["running shoes", "protein powder"],
  "countries": ["DE", "FR", "IT"],
  "dateFrom": "2026-07-01",
  "dateTo": "2026-08-15",
  "maxItems": 100,
  "includeAdDetails": true
}
```

| Field | What it does |
|---|---|
| `searchTerms` | Matches advertiser names and ad captions. Up to 20 per run; the row budget is split evenly between them. |
| `advertiserNames` | Registered advertiser names, e.g. `NIKE Retail B.V.`. Each is resolved against the library's own advertiser register first, then that entity's ads are pulled. A brand word like `Nike` picks up every registered entity whose name starts with it. |
| `adLibraryUrls` | Paste addresses straight from library.tiktok.com/ads. The country, date range, advertiser, sort order and format filters already in the URL get read back out and used. |
| `countries` | Two-letter codes, or `all`. Only these exist: 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. On its own, with no search terms, it means "every ad running in these countries". |
| `dateFrom` / `dateTo` | `YYYY-MM-DD`. Defaults to the last 30 days. An earlier date than 2022-10-01 is moved forward to it. |
| `maxItems` | Total rows across every target. Default 20, ceiling 20,000. |
| `includeAdDetails` | On by default. This fetches the per-ad transparency record, and it's the only source of `landingUrl`, `callToAction`, `advertisingObjective`, `advertiserRegistryCountry`, `countriesTargeted`, `targetAudienceSize` and `impressionsByCountry`. Turn it off for a lighter run with those fields null. |
| `adFormat` | `all`, `video`, `image` or `text`. |
| `adStatus` | `all`, `active` (still running) or `inactive` (stopped). |
| `sortBy` | `create_time,desc` (default), `last_shown_date,desc`, `impression,desc` and the ascending versions. |
| `proxyUrls` | Leave empty unless you already pay for proxy servers and want traffic to leave through them. |
| `sessionCookies` | Leave empty. Nothing is logged in and the archive doesn't need it. The field is a safety net: if TikTok ever stops serving anonymous callers, your own `sessionid` here gets the run a rate limit nobody else shares. |

Empty input gives you one labelled sample row, free, so you can see the shape first.

### Output

```json
{
  "ok": true,
  "charged": true,
  "recordType": "ad",
  "searchTerm": "running shoes",
  "adId": "1873543311265906",
  "advertiserName": "B - SPORTING LIMITED",
  "caption": "Shop our range of running shoes & clothing from all the top brands at the best prices",
  "firstShownDate": "2026-08-15T00:00:00.000Z",
  "lastShownDate": "2026-08-15T00:00:00.000Z",
  "countriesTargeted": ["GB"],
  "uniqueUsersSeen": "0-1K",
  "videoUrl": "https://library.tiktok.com/api/v1/cdn/1786906839/video/...",
  "thumbnailUrl": "https://p16-common-sign.tiktokcdn.com/tos-alisg-p-0051c001-sg/...",
  "landingUrl": "https://www.sportsshoes.com/?utm_source=tiktok&utm_medium=cpc&...",
  "callToAction": null,
  "adFormat": "video",
  "adDetailUrl": "https://library.tiktok.com/ads/detail/?ad_id=1873543311265906",
  "advertiserId": "6877843515099841282",
  "advertiserRegistryCountry": "United Kingdom",
  "paidBy": "B - SPORTING LIMITED",
  "advertisingObjective": null,
  "adStatus": "active",
  "imageUrls": ["https://p16-common-sign.tiktokcdn.com/tos-alisg-p-0051c001-sg/..."],
  "targetAudienceSize": "24.5M-30.0M",
  "impressionsByCountry": { "GB": "0-1K" },
  "targetedAgeGroups": ["18-24", "25-34", "35-44", "45-54", "55+"],
  "targetedGenders": ["female", "male", "unknown"],
  "searchedRegions": ["all"],
  "rejectionReason": null,
  "scrapedAt": "2026-08-16T19:00:39.536Z"
}
```

Things worth knowing about specific fields:

- `uniqueUsersSeen` and `impressionsByCountry` are bands, not numbers — `0-1K`, `100K-1M`. That's how
  TikTok publishes them. There is no exact impression count anywhere in the archive.
- `advertiserRegistryCountry` is where the paying entity is registered, which is often not where the
  ad ran. Chinese and US entities buying ads in Germany, France and Spain are common in the archive.
- `callToAction` and `advertisingObjective` are null in the row above because that advertiser didn't
  set them. Plenty of ads carry them (`Shop now` / `Sales`, `View TikTok profile` /
  `Community interaction`). Null means the archive has no value, not that the fetch failed.
- `videoUrl` points at TikTok's own CDN through a signed path. Download it during or soon after the
  run rather than months later.
- `firstShownDate` and `lastShownDate` are day-granular. TikTok doesn't publish a time.

### Limits

- **Coverage is EEA + GB, CH, NO, IS, LI. There is no US edition of this library.** `region=US` is
  answered with an error by TikTok itself. If you need US ad data this is the wrong tool.
- Nothing before 2022-10-01 exists in the archive.
- TikTok throttles this API hard, and the throttle is aggressive on cloud addresses. When a search
  gets throttled to the point of no results you get an uncharged `RATE_LIMITED` diagnostic row and a
  succeeded run, not a failure and not a bill. Retrying later is usually the fix; narrower searches
  and lower `maxItems` help.
- Pages come back 12 rows at a time whatever the run asks for. That's server-side, not a setting.
- Sorting by `last_shown_date` makes TikTok's own library repeat some ads across pages and skip
  others. `create_time,desc` does not, so that is the default. Ad ids are de-duplicated across the
  whole run either way.
- No exact impression counts, no spend, no ad performance. The archive publishes bands and dates,
  and nothing here invents the rest.
- Turn `includeAdDetails` off and the landing URL, CTA, objective, targeting and audience-size fields
  all come back null. Those live only on the per-ad record.
- Hard ceilings: 20 search terms, 20 advertiser names, 20 URLs, 20,000 rows per run.

### Questions

**Can I get US TikTok ads?** No. The library is an EU/UK transparency archive and TikTok doesn't
publish a US edition of it.

**Do I need a TikTok account?** No. The run mints its own guest token from a public endpoint. The
`sessionCookies` field exists only as a fallback if that ever stops working.

**What does a `RATE_LIMITED` row mean?** TikTok answered "system busy" to every attempt for that
search. You aren't charged for it. Try again later or narrow the search.

**Can I get the ad creative file?** You get `videoUrl`, `thumbnailUrl` and `imageUrls`. Fetching and
storing the media itself is on you.

**Can I schedule it?** Yes. Nothing is held between runs. `adId` is stable, so diff on it.

# Actor input Schema

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

Keywords to search for. Matches both advertiser names and ad captions, so "running shoes" returns every ad whose copy mentions running shoes. Up to 20 per run; the row budget is shared evenly between them.

## `advertiserNames` (type: `array`):

Registered advertiser names, for example "NIKE Retail B.V.". Each is resolved against the library's own advertiser register first, then that advertiser's complete ad set is pulled. Typing a brand word like "Nike" picks up every registered entity whose name starts with it.

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

Paste addresses straight from library.tiktok.com/ads. The country, date range, advertiser, sort order and format filters already in the URL are read back out and used as they are.

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

Two-letter codes for the countries an ad was shown in, or "all". Only these exist in this library: 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. Leave empty to search all of them. On its own, with no search terms, it means "every ad running in these countries".

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

The start of the window an ad must have run in, as YYYY-MM-DD. Defaults to 30 days ago. The library holds nothing before 2022-10-01 and an earlier date is moved forward to it.

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

The end of the window, as YYYY-MM-DD. Defaults to today.

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

Total number of ads to return across every search term, advertiser and URL. The budget is shared evenly between them. Keep it low while you are testing - you pay per row.

## `includeAdDetails` (type: `boolean`):

On by default. This is the only source of the landing page URL, the call to action, the campaign objective, the advertiser's registry country, the full list of targeted countries, the audience size and the per-country impression bands. Turn it off for a faster, lighter run with those fields left null.

## `adFormat` (type: `string`):

Return every format, or only video, image or text ads.

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

Return every ad, only ads still running, or only ads that have stopped.

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

How the library should order the results before they are paged. Published-date descending is the default because it is the ordering the library itself repeats and skips least.

## `proxyUrls` (type: `array`):

Leave this empty. By default the run rotates a large pool of addresses that cost you nothing per gigabyte. Fill it in only if you specifically want the traffic to leave through proxy servers you already pay for, in the form http://user:pass@host:port.

## `sessionCookies` (type: `array`):

Optional, and almost never needed. The Commercial Content Library is a public transparency archive and this Actor reads it without any account. Supply your own TikTok account cookie only as a safety net for the day the library stops serving anonymous callers. In Chrome: F12 -> Application -> Cookies -> tiktok.com, and copy the value of sessionid. Anyone with this value can act as your account, so treat it like a password.

## Actor input object example

```json
{
  "searchTerms": [
    "running shoes"
  ],
  "countries": [
    "DE"
  ],
  "dateFrom": "2026-07-01",
  "dateTo": "2026-08-15",
  "maxItems": 20,
  "includeAdDetails": true,
  "adFormat": "all",
  "adStatus": "all",
  "sortBy": "create_time,desc"
}
```

# Actor output Schema

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

Every row in the default dataset: searchTerm, adId, advertiserName, caption, firstShownDate, lastShownDate, countriesTargeted, uniqueUsersSeen, videoUrl, thumbnailUrl, landingUrl, callToAction, adFormat, adDetailUrl, advertiserId, advertiserRegistryCountry, paidBy, advertisingObjective, adStatus, imageUrls, targetAudienceSize, impressionsByCountry, targetedAgeGroups, targetedGenders, searchedRegions, rejectionReason, scrapedAt. An empty, blocked or unmatched run returns a single uncharged row explaining what happened instead.

# 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": [
        "running shoes"
    ],
    "countries": [
        "DE"
    ],
    "maxItems": 20,
    "includeAdDetails": true,
    "adFormat": "all",
    "adStatus": "all",
    "sortBy": "create_time,desc"
};

// Run the Actor and wait for it to finish
const run = await client.actor("dami_studio/tiktok-ads-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": ["running shoes"],
    "countries": ["DE"],
    "maxItems": 20,
    "includeAdDetails": True,
    "adFormat": "all",
    "adStatus": "all",
    "sortBy": "create_time,desc",
}

# Run the Actor and wait for it to finish
run = client.actor("dami_studio/tiktok-ads-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": [
    "running shoes"
  ],
  "countries": [
    "DE"
  ],
  "maxItems": 20,
  "includeAdDetails": true,
  "adFormat": "all",
  "adStatus": "all",
  "sortBy": "create_time,desc"
}' |
apify call dami_studio/tiktok-ads-scraper --silent --output-dataset

```

## MCP server setup

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