# X Ads Repository Scraper & Competitor Monitor (`lexxer/x-ads-repository-monitor`) Actor

Extract current ads served on X across European markets and emit only new, changed, or removed competitor campaigns on scheduled runs. No X login or developer key required.

- **URL**: https://apify.com/lexxer/x-ads-repository-monitor.md
- **Developed by:** [Lexxer ​](https://apify.com/lexxer) (community)
- **Categories:** Social media, Marketing
- **Stats:** 3 total users, 2 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$0.10 / 1,000 emitted ad or campaign changes

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

## X Ads Repository Scraper & Competitor Monitor

Extract current advertisements served on X across European markets, then monitor competitors for new, changed, and removed campaigns. The Actor uses X's public Ads Repository workflow and does not require an X login, developer account, or customer-supplied API key.

### What it returns

Each successful row can include:

- advertiser handle, ID, and disclosed account name
- tweet, line-item, and account identifiers
- tweet creation timestamp derived from the stable public tweet ID
- country where the ad was served
- funding instrument type and currency
- approval status
- current creative text and author when the public tweet remains embeddable
- targeting names and types
- impressions and reach
- stable ad key and content hash
- `NEW`, `CHANGED`, `UNCHANGED`, or `REMOVED` change status
- first-seen and last-seen timestamps

X does not currently expose creative text or media through this repository response. The Actor returns a canonical tweet URL when a tweet ID is present, but it does not claim that the tweet remains publicly viewable.

Creative enrichment uses X's public oEmbed response. Deleted, protected, or unavailable tweets keep their repository metrics and receive a clear creative-enrichment status instead of failing the whole advertiser.

### Supported markets

The Apify input form shows normal country names, so Console users do not need to know country codes. API users may pass two-letter codes. Ready-made presets include:

- **DACH:** Germany, Austria, and Switzerland
- **Nordics:** Denmark, Finland, Iceland, Norway, and Sweden
- **United Kingdom & Ireland**
- **All supported European markets**
- **Custom:** choose any combination from the Markets field

Advertisers are not limited to the examples. Enter any public X handle, `@handle`, or profile URL—up to 50 per run.

### What customers use it for

- receive an alert when a competitor launches or removes an ad
- compare campaign reach and targeting between European markets
- collect current competitor messaging and disclosed funding information
- build advertising-transparency and compliance datasets
- identify long-running campaigns and changes in market coverage
- filter campaigns by creative or targeting keywords and minimum reach/impressions
- rank results by impressions, reach, recency, or advertiser

### Quick start

```json
{
  "advertisers": ["Apple", "SamsungMobile"],
  "marketPreset": "dach",
  "startDate": "2026-01-01",
  "outputMode": "snapshot"
}
```

The first run should use `snapshot`. For scheduled monitoring, switch to:

```json
{
  "advertisers": ["Apple", "SamsungMobile"],
  "countries": ["FR", "DE"],
  "startDate": "2026-01-01",
  "outputMode": "changes",
  "monitorKey": "consumer-tech-eu"
}
```

The first `changes` run emits every discovered ad as `NEW`. Later runs emit only new, changed, and removed rows. Attach an Apify schedule and run-success webhook to integrate the change dataset with email, Slack, Make, Zapier, or another automation system.

Routine impression and reach growth does not produce a `CHANGED` alert. Changes are reserved for disclosed campaign properties such as approval status, targeting, funding, or advertiser details. Updated metrics remain available in snapshot output.

Every run summary also includes total ads, impressions, and reach plus breakdowns by advertiser and country for quick market comparisons.

### Reliability behavior

X sometimes responds with rate limits or `Over capacity`. The Actor uses conservative concurrency, a delay between requests, exponential backoff, and retryable error classification. A failed advertiser produces a clear, uncharged failure row and does **not** cause its previously tracked ads to be marked as removed.

Empty results are valid and uncharged. They mean X returned no matching ads for that advertiser, country selection, and date range.

### Monitoring state

Change tracking is stored in the Actor's default key-value store under `MONITOR_STATE`. Each `monitorKey` has independent history. Keep the same Actor task and monitor key for scheduled runs. Deleting the key-value store resets the monitor and causes all current ads to be emitted as `NEW` again.

### Pricing recommendation

Launch pricing charges **$0.0001 per emitted ad/change row** ($0.10 per 1,000) plus Apify's tiny synthetic Actor-start event, currently calculated at $0.00005 for this 256 MB Actor. Failure rows and empty searches are not billable result events. This keeps ordinary runs inexpensive while covering fixed startup cost and leaving a small positive margin.

### Known limitations

- Coverage is limited to the countries and advertisements exposed by X's Ads Repository.
- X can change or remove the public endpoints.
- Repository results may be delayed relative to ad delivery.
- The source can return temporary over-capacity errors.
- Metrics and targeting are only as complete as X's public disclosure.
- `REMOVED` means the ad disappeared from the same monitored query, not necessarily that X permanently deleted it.

### Local development

```bash
npm install
npm test
npm run check
```

The normal tests use deterministic mocked responses and spend no Apify credits. A public live smoke test is opt-in:

```bash
RUN_LIVE_TESTS=1 npm run test:live
```

### Responsible use

Use the Actor for legitimate advertising research, compliance, journalism, and competitor analysis. The output comes from X's public advertising-transparency system. Users remain responsible for applicable law and platform terms.

# Actor input Schema

## `advertisers` (type: `array`):

X handles, profile URLs, or @handles. Up to 50 per run.

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

Countries covered by X's Ads Repository. Use two-letter codes.

## `marketPreset` (type: `string`):

Choose a ready-made region or Custom to use the Markets field above.

## `startDate` (type: `string`):

Inclusive delivery date in YYYY-MM-DD format. Current repository data begins in 2023.

## `endDate` (type: `string`):

Inclusive delivery date in YYYY-MM-DD format. Leave empty through the API to use today.

## `outputMode` (type: `string`):

Snapshot returns every current ad. Changes returns only NEW, CHANGED, and REMOVED ads compared with prior runs using the same monitor key.

## `monitorKey` (type: `string`):

Optional stable name for scheduled change tracking, such as apple-eu. A deterministic key is generated when omitted.

## `maxAdsPerAdvertiser` (type: `integer`):

Safety cap applied after X returns the current result set.

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

Optional case-insensitive terms. A row is returned when its creative, advertiser, or targeting contains any term.

## `minImpressions` (type: `integer`):

Return ads with at least this many disclosed impressions.

## `minReach` (type: `integer`):

Return ads with at least this much disclosed reach.

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

Choose the order of emitted rows.

## `concurrency` (type: `integer`):

The conservative default reduces X over-capacity errors.

## `requestDelayMs` (type: `integer`):

Milliseconds between public X requests. Increase this if X reports over-capacity errors.

## `maxRetries` (type: `integer`):

Retries timeouts, rate limits, and X over-capacity responses with exponential backoff.

## `enrichCreatives` (type: `boolean`):

Resolve each unique public tweet through X's oEmbed service. Adds requests but produces much more useful competitor intelligence.

## `includeCreativeHtml` (type: `boolean`):

Add X's complete oEmbed HTML. Leave disabled for compact datasets.

## `includeDebugFields` (type: `boolean`):

Advanced option. Adds X's raw debug payloads and can make dataset rows much larger.

## Actor input object example

```json
{
  "advertisers": [
    "Apple",
    "SamsungMobile"
  ],
  "countries": [
    "FR",
    "DE"
  ],
  "marketPreset": "custom",
  "startDate": "2026-01-01",
  "outputMode": "snapshot",
  "maxAdsPerAdvertiser": 1000,
  "searchTerms": [],
  "minImpressions": 0,
  "minReach": 0,
  "sortBy": "impressions-desc",
  "concurrency": 1,
  "requestDelayMs": 750,
  "maxRetries": 4,
  "enrichCreatives": true,
  "includeCreativeHtml": false,
  "includeDebugFields": false
}
```

# Actor output Schema

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

Successful ad rows are eligible for pay-per-event billing. Failure rows remain uncharged.

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

Advertiser, ad, change, failure, and truncation counts.

# 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 = {
    "advertisers": [
        "Apple",
        "SamsungMobile"
    ],
    "startDate": "2026-01-01"
};

// Run the Actor and wait for it to finish
const run = await client.actor("lexxer/x-ads-repository-monitor").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 = {
    "advertisers": [
        "Apple",
        "SamsungMobile",
    ],
    "startDate": "2026-01-01",
}

# Run the Actor and wait for it to finish
run = client.actor("lexxer/x-ads-repository-monitor").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 '{
  "advertisers": [
    "Apple",
    "SamsungMobile"
  ],
  "startDate": "2026-01-01"
}' |
apify call lexxer/x-ads-repository-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,lexxer/x-ads-repository-monitor"
        }
    }
}

```

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/ac3TL1PpXFcaop4uD/builds/sRwrZIF7R9RUd6pL7/openapi.json
