Cross-Channel Ad Intelligence Monitor
Pricing
from $25.00 / 1,000 successful advertiser-channel checks
Cross-Channel Ad Intelligence Monitor
Cross-channel competitor ad monitoring across public Meta, Google, LinkedIn, and TikTok transparency data. Detect new, changed, reactivated, and safely ended ads, plus disclosed geography and landing-page changes, with source-health evidence. Unofficial beta; bring your own compatible sources.
Pricing
from $25.00 / 1,000 successful advertiser-channel checks
Rating
0.0
(0)
Developer
Progamadores.com
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
7 days ago
Last modified
Categories
Share
Cross-channel ad intelligence and competitor ad monitoring for public transparency data from Meta, Google, LinkedIn, and TikTok. Detect new ads, creative changes, landing-page changes, lifecycle changes, and changes in disclosed delivery countries from one evidence-backed workflow.
Unofficial beta. This Actor is not affiliated with, endorsed by, or sponsored by Meta, Google, LinkedIn, TikTok, or the providers of compatible source Actors. It does not grant official platform API access or bypass access controls.
The Actor turns compatible source records into a consistent timeline of NEW, CHANGED, REACTIVATED, POSSIBLY_INACTIVE, and INACTIVE_CONFIRMED events. It is designed for agencies, brands, growth teams, and researchers that need repeatable ad monitoring without treating incomplete public ad libraries as complete campaign data.
It consumes datasets you select or output from compatible source Actors running in your own Apify account. Every result retains source-health evidence so downstream systems can distinguish a verified complete scan from a partial, blocked, truncated, unsupported, or failed check.
What this cross-channel ad intelligence Actor delivers
- One normalized competitor-ad model across Meta Ad Library, Google Ads Transparency Center, LinkedIn Ad Library, and TikTok Commercial Content Library records.
- New-ad detection and evidence-backed ad lifecycle monitoring.
- Creative change detection for observable headlines, body text, descriptions, calls to action, formats, and creative asset URLs.
- Landing-page and offer change detection when bounded landing-page analysis is enabled.
- Limited targeting change detection for source-disclosed delivery countries represented by the normalized
countriesfield. - Deterministic creative analysis for observable hooks, offers, calls to action, and funnel signals; no paid AI API is required in
RULESmode. - Source-health diagnostics that prevent incomplete scans from creating false deactivation alerts.
- Default Dataset records, a machine-readable
SUMMARY.json, and a human-readableREPORT.html. - Optional idempotent webhook delivery with HMAC-SHA256 signing.
This Actor does not infer ad spend, clicks, impressions that a source does not disclose, conversions, CTR, CPA, revenue, or ROAS. If you need an ad spend scraper or an ads-account analytics connector, this is not that product. Ad longevity and repeated observation are research signals, not proof of performance.
Concrete use cases
Competitor launch monitoring
Run the same monitorId daily or weekly and route NEW events to a product-marketing or sales channel. This can reveal newly observable campaigns, messages, offers, formats, and destination pages across the configured public sources.
Creative and offer change detection
Use CHANGED records to detect when an observed ad changes its headline, body, description, CTA, creative URL, format, landing URL, landing-page content hash, or offer hash. The event includes before, after, changedFields, and source evidence.
Limited geographic targeting change detection
Track changes to the normalized countries field when a source discloses delivery geography. This is intentionally narrower than full audience-targeting intelligence: interests, lookalikes, account audiences, bids, and undisclosed demographics are not inferred.
Agency monitoring across brands and channels
Submit up to 100 targets and any subset of the four supported platforms. Each target/platform pair is isolated, so one unavailable source does not hide results from the others. Use stable eventId values to deduplicate retries in a CRM, warehouse, Slack bot, or client dashboard.
Landing-page and funnel research
Enable bounded landing-page analysis to capture public page metadata, redirects, offer text, content fingerprints, and common technology signals. Requests enforce DNS/SSRF, redirect, MIME-type, response-size, and timeout limits.
Evidence-backed advertising research
Preserve source URLs, completeness diagnostics, observation times, and cautious inactivity states for internal competitive research. The records are not a legal archive and do not replace direct verification in the relevant transparency library.
Exactly what can trigger a change event
The material fingerprint compares only stable, user-visible normalized fields:
| Group | Compared fields |
|---|---|
| Copy | headline, body, description, cta |
| Destination | landing_url, display_url |
| Landing analysis | landing_page_hash, landing_offer_hash |
| Creative | ad_format, creative_urls |
| Disclosed geography | countries |
Platform-specific metadata can still appear in a normalized after.metadata object when supplied by a compatible source, but it is not automatically a material-change signal. In particular, “targeting change detection” here means changes to normalized, source-disclosed fields such as countries—not access to private campaign targeting.
How source access works
| Mode | Behavior | Best for |
|---|---|---|
DATASET | Reads only the platform datasets selected with the four resource pickers. Every selected platform must have a dataset. | Controlled validation, reproducible analysis, and avoiding child Actor runs. |
EXTERNAL_ACTOR | Calls compatible source Actors under your Apify account. Override them with sourceActorIds. | Scheduled collection after you have tested and pinned compatible source Actors. |
AUTO | Prefers a selected dataset for each platform, then tries a compatible configured source Actor. If neither is available, the check is marked UNSUPPORTED. | Mixed workflows. |
The dataset selectors request READ permission only:
metaSourceDatasetgoogleSourceDatasetlinkedinSourceDatasettiktokSourceDataset
Selected datasets are not modified. Source schemas change over time, so incompatible or incomplete records are skipped or marked partial in diagnostics instead of being silently fabricated.
sourceActorIds is an object keyed by META, GOOGLE, LINKEDIN, or TIKTOK. These are bring-your-own integrations, not endorsements or official platform connectors. A child Actor can require additional permissions, consume platform resources, and apply its own pricing. maxSourceChargeUsd limits each child Actor call; also set an Apify run-level maximum charge when using paid sources.
This Actor runs with LIMITED_PERMISSIONS, so every child Actor selected through sourceActorIds must also support LIMITED_PERMISSIONS. An incompatible full-permission child is reported as a source failure instead of silently weakening this Actor's permission level.
Platform coverage and honest limitations
| Platform | Supported evidence | Important limits |
|---|---|---|
| Meta | Meta Ad Library records supplied by a selected dataset or compatible source Actor. Common creative text, media, landing URLs, advertiser identity, and run dates are normalized when present. | Availability and inactive-history coverage depend on the public library, region, ad category, and source provider. This Actor does not include Meta Marketing API access. |
| Google Ads Transparency Center records supplied by the configured source. Advertiser, creative, format, regions, dates, and landing evidence are retained when disclosed. | This is not a Google Ads account connector and does not expose campaign performance. Fields and history vary by region and provider. | |
| LinkedIn Ad Library records, including image, video, carousel, document, and thought-leader formats when present. Advertiser, payer, dates, and disclosed EU impressions or targeting are preserved. | Restricted or unavailable ads can omit previews, payer details, targeting, or impression ranges. No default official API access is claimed. | |
| TikTok | TikTok Commercial Content Library records supplied by an authorized dataset or compatible Actor. Advertiser/payer, targeting, impression ranges, dates, media, and landing evidence are retained when disclosed. | Commercial Content Library advertising coverage is European rather than global. TikTok Creative Center Top Ads is a separate curated inspiration set and is always treated as partial coverage. |
For current TikTok scope, see the official Commercial Content Library information and supported countries. Creative Center's Top Ads is a selected collection; the Actor never converts those labels into verified ROAS or other account-level performance.
Quick start with an existing source Dataset
- Produce or choose a compatible source Dataset in your Apify account.
- Create a new Actor task and set
providerModetoDATASET. - Select one Dataset for every platform in
platforms. - Add stable advertiser IDs or public library URLs to each target whenever possible.
- Start with
dryRun: trueto validate normalization and source health. Dry run does not establish a persistent baseline. - Set
dryRun: falseand keepinitialRunBehavior: BASELINE_ONLYfor the first persistent run. - Schedule later runs with exactly the same
monitorId. Do not overlap runs for the same monitor.
This is a valid, copyable input for one Meta target. Replace the two placeholders before running it:
{"monitorId": "fashion-es-weekly","targets": [{"id": "example-brand","name": "Example Brand","domain": "example.com","metaPageId": "YOUR_CONFIRMED_META_PAGE_ID"}],"platforms": ["META"],"country": "ES","providerMode": "DATASET","metaSourceDataset": "YOUR_META_DATASET_ID","maxAdsPerTarget": 100,"missingScansBeforeInactive": 2,"initialRunBehavior": "BASELINE_ONLY","includeLandingAnalysis": false,"maxLandingPagesPerRun": 25,"analysisMode": "RULES","dryRun": true}
Do not copy the example advertiser values into a production monitor. Confirm the advertiser identity in the source library first.
Exact input reference
Unknown top-level fields are rejected.
| Field | Type | Required | Default / allowed values | Purpose |
|---|---|---|---|---|
monitorId | string | Yes | 3–80 characters; letters, numbers, _, - | Stable history namespace. Reuse it for every persistent run of the same monitor. |
targets | object[] | Yes | 1–100 items | Brands or advertisers to monitor. Target IDs must be unique. |
platforms | string[] | Yes | Unique subset of META, GOOGLE, LINKEDIN, TIKTOK | Sources to scan independently. |
country | string | No | ES; exactly two letters | Country used for source queries and regional availability. Normalized to uppercase. |
maxAdsPerTarget | integer | No | 100; 1–1,000 | Per-target/per-platform safety cap. Reaching it marks the check truncated. |
missingScansBeforeInactive | integer | No | 2; 2–10 | Consecutive complete missing scans required for INACTIVE_CONFIRMED. |
providerMode | string | No | AUTO; AUTO, EXTERNAL_ACTOR, DATASET | Selects how compatible source records are obtained. |
metaSourceDataset | string | Conditional | — | Read-only Meta source Dataset ID. Required for Meta in DATASET mode. |
googleSourceDataset | string | Conditional | — | Read-only Google source Dataset ID. Required for Google in DATASET mode. |
linkedinSourceDataset | string | Conditional | — | Read-only LinkedIn source Dataset ID. Required for LinkedIn in DATASET mode. |
tiktokSourceDataset | string | Conditional | — | Read-only TikTok source Dataset ID. Required for TikTok in DATASET mode. |
sourceActorIds | object | No | {}; keys: four platform names | Compatible child Actor IDs in username/name or Actor-ID form. |
maxSourceChargeUsd | number | No | 1; 0.01–100 | Maximum charge applied to each child source Actor call. |
initialRunBehavior | string | No | BASELINE_ONLY; or EMIT_EXISTING_AS_NEW | Controls whether existing ads become alerts on the first persistent run. |
includeLandingAnalysis | boolean | No | true | Enables bounded fetching and analysis of public landing pages. |
maxLandingPagesPerRun | integer | No | 25; 1–100 | Global cap on unique landing pages after URL deduplication. |
analysisMode | string | No | RULES; RULES or OFF | Deterministic creative analysis or no creative analysis. |
webhookUrl | string | No | Public HTTPS URL | Receives the run summary and persisted change events. Stored as a secret input. |
webhookHmacSecret | string | No | Up to 512 characters | Optional HMAC-SHA256 signing secret. Stored as a secret input. |
dryRun | boolean | No | false | Suppresses monitor-state writes, webhooks, and this Actor's custom billing events. Child Actors can still charge. |
Target object fields
The published input schema requires id, name, and domain for every target. Unknown target fields are rejected by the schema.
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | Yes | Stable internal ID, 1–80 characters. Do not change it between runs. |
name | string | Yes | Human-readable advertiser name, 1–160 characters. |
domain | string | Yes | Canonical advertiser domain without a path. |
metaPageId | string | No | Confirmed stable Meta Page ID. |
metaPageUrl | string | No | Public advertiser Meta Page URL. |
metaAdLibraryUrl | string | No | Public Meta Ad Library URL identifying the advertiser. |
googleAdvertiserId | string | No | Stable Google Ads Transparency advertiser ID. |
googleAdvertiserUrl | string | No | Public Google Ads Transparency advertiser URL. |
linkedinCompanyId | string | No | Stable LinkedIn company or advertiser ID. |
linkedinAdLibraryUrl | string | No | Public LinkedIn Ad Library URL. |
tiktokAdvertiserId | string | No | Stable advertiser ID supplied by the configured TikTok source. |
tiktokAdvertiserName | string | No | Exact source advertiser name; use only when a stable ID is unavailable. |
Example source Actor overrides:
{"providerMode": "EXTERNAL_ACTOR","sourceActorIds": {"META": "YOUR_USERNAME/YOUR_COMPATIBLE_META_ACTOR","GOOGLE": "YOUR_USERNAME/YOUR_COMPATIBLE_GOOGLE_ACTOR"},"maxSourceChargeUsd": 1}
The fragment above is not a complete Actor input; merge it into an input containing monitorId, targets, and platforms.
Output contract
The Output tab exposes three resources:
| Output | Location | Contents |
|---|---|---|
| Change events and run summary | Default Dataset | Zero or more CHANGE_EVENT items followed by one RUN_SUMMARY. |
| Human-readable report | REPORT.html in the default key-value store | Source health, metrics, and a change table. |
| Machine-readable summary | SUMMARY.json in the default key-value store | The same final summary object nested in the Dataset's RUN_SUMMARY.summary. |
Use recordType as the Dataset discriminator. Use eventId to deduplicate change retries and recordId plus scanId to deduplicate run summaries.
Illustrative CHANGE_EVENT JSON
The following object is illustrative: IDs, timestamps, URLs, text, and counts are invented. Its field names and nesting follow the implemented v1.0 serializer. Nullable values and source-specific metadata vary by record.
{"recordType": "CHANGE_EVENT","schemaVersion": "1.0","eventId": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","monitorId": "fashion-es-weekly","scanId": "fashion-es-weekly-EXAMPLE_APIFY_RUN_ID","observedAt": "2026-08-07T10:00:00+00:00","platform": "META","targetId": "example-brand","targetName": "Example Brand","changeType": "CHANGED","severity": "HIGH","adKey": "meta-ad-123","changedFields": ["headline","cta","countries"],"before": {"headline": "Summer collection","cta": "LEARN_MORE","countries": ["ES"]},"after": {"platform": "META","ad_id": "meta-ad-123","advertiser_id": "YOUR_CONFIRMED_META_PAGE_ID","advertiser_name": "Example Brand","observed_at": "2026-08-07T10:00:00+00:00","is_active": true,"headline": "Summer offer: 20% off","body": "Illustrative creative copy.","description": null,"cta": "SHOP_NOW","landing_url": "https://example.com/summer","landing_page_hash": null,"landing_offer_hash": null,"display_url": "https://example.com/","source_url": "https://www.facebook.com/ads/library/?id=meta-ad-123","ad_format": "IMAGE","creative_urls": ["https://cdn.example.com/creative/meta-ad-123.jpg"],"first_shown_at": "2026-08-01T00:00:00+00:00","last_shown_at": null,"countries": ["ES", "PT"],"metadata": {"identity_resolution": {"confidence": 1.0}},"analysis": {}},"evidence": {"source_health": "FULL","source_url": "https://www.facebook.com/ads/library/?id=meta-ad-123","field_changes": {"headline": {"previous": "Summer collection","current": "Summer offer: 20% off"},"cta": {"previous": "LEARN_MORE","current": "SHOP_NOW"},"countries": {"previous": ["ES"],"current": ["ES", "PT"]}}},"confidence": 1.0,"message": "Example Brand changed ad fields: headline, cta, countries."}
confidence concerns advertiser identity/comparison confidence when the source provides enough evidence. It is not an ad-performance score.
Illustrative RUN_SUMMARY JSON
This is also illustrative. The complete keys shown below are produced by the current runtime; values depend on source health, billing configuration, landing analysis, storage IDs, and webhook configuration.
{"recordType": "RUN_SUMMARY","schemaVersion": "1.0","recordId": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","monitorId": "fashion-es-weekly","scanId": "fashion-es-weekly-EXAMPLE_APIFY_RUN_ID","observedAt": "2026-08-07T10:00:00+00:00","summary": {"schemaVersion": "1.0","monitorId": "fashion-es-weekly","scanId": "fashion-es-weekly-EXAMPLE_APIFY_RUN_ID","baselineCreated": false,"dryRun": false,"adsObserved": 42,"changesDetected": 1,"changesByType": {"CHANGED": 1},"platformHealth": [{"platform": "META","targetId": "example-brand","health": "FULL","complete": true,"truncated": false,"items": 42,"warnings": [],"errors": []}],"limitations": ["Coverage is limited to records visible in each configured public source.","Persistence and longevity are observational signals, not proof of performance.","Spend, clicks, conversions, and ROAS are not inferred."],"observedAt": "2026-08-07T10:00:00+00:00","baselinePersisted": false,"checksRequested": 1,"checksExecuted": 1,"checksAlreadyProcessed": 0,"checksChargeable": 1,"checksCharged": 1,"checksSkippedByBudget": 0,"billingStatus": "CHARGED","enrichment": {"landingPagesRequested": 0,"landingPagesCompleted": 0,"landingPageErrors": 0,"adsAnalyzed": 42},"storage": {"normalizedAdsDatasetId": "EXAMPLE_NORMALIZED_ADS_DATASET_ID","diagnosticsDatasetId": "EXAMPLE_DIAGNOSTICS_DATASET_ID"},"checksPreviouslyCharged": 0,"checksOutstandingBeforeCharge": 1,"checksChargedThisAttempt": 1,"webhook": {"delivered": false,"reason": "NOT_CONFIGURED"}}}
The default Dataset intentionally contains changes and the final summary—not every raw source row. Persistent normalized observations and diagnostics live in the named storage IDs reported under summary.storage.
API integration: cURL with an asynchronous run
For monitoring jobs, prefer the asynchronous REST endpoint. The synchronous dataset endpoint can hold a connection for at most 300 seconds; a timeout does not abort the underlying run. The script below requires Bash, curl, and jq. It starts the Actor, waits in 60-second server-side intervals, checks the terminal status, downloads the Dataset, and separates changes from the run summary.
export APIFY_TOKEN='YOUR_APIFY_TOKEN'RUN_RESPONSE="$(curl --fail --silent --show-error --location \--request POST \'https://api.apify.com/v2/actors/dekaz~cross-channel-ad-intelligence-monitor/runs' \--header "Authorization: Bearer ${APIFY_TOKEN}" \--header 'Content-Type: application/json' \--data-binary @- <<'JSON'{"monitorId": "fashion-es-weekly","targets": [{"id": "example-brand","name": "Example Brand","domain": "example.com","metaPageId": "YOUR_CONFIRMED_META_PAGE_ID"}],"platforms": ["META"],"country": "ES","providerMode": "DATASET","metaSourceDataset": "YOUR_META_DATASET_ID","initialRunBehavior": "BASELINE_ONLY","includeLandingAnalysis": false,"analysisMode": "RULES","dryRun": true}JSON)"RUN_ID="$(printf '%s' "$RUN_RESPONSE" | jq -er '.data.id')"while true; doRUN_RESPONSE="$(curl --fail --silent --show-error --location \"https://api.apify.com/v2/actor-runs/${RUN_ID}?waitForFinish=60" \--header "Authorization: Bearer ${APIFY_TOKEN}")"STATUS="$(printf '%s' "$RUN_RESPONSE" | jq -er '.data.status')"case "$STATUS" inSUCCEEDED) break ;;FAILED|ABORTED|TIMED-OUT)printf 'Actor run %s ended with status %s\n' "$RUN_ID" "$STATUS" >&2exit 1;;esacdoneDATASET_ID="$(printf '%s' "$RUN_RESPONSE" | jq -er '.data.defaultDatasetId')"curl --fail --silent --show-error --location \"https://api.apify.com/v2/datasets/${DATASET_ID}/items?clean=true&limit=1000" \--header "Authorization: Bearer ${APIFY_TOKEN}" \| jq '{changeEvents: [.[] | select(.recordType == "CHANGE_EVENT")],runSummary: ([.[] | select(.recordType == "RUN_SUMMARY")] | last)}'
This one-target example cannot exceed the requested 1,000-item download limit. For larger matrices, paginate the Dataset endpoint or use an official client as shown below. For production persistence, change dryRun to false only after inspecting the dry-run summary.
API integration: Python
Install the official client:
$pip install apify-client
Then run the Actor, iterate the complete Dataset, and process records by discriminator:
import osfrom apify_client import ApifyClientACTOR_ID = "dekaz/cross-channel-ad-intelligence-monitor"actor_input = {"monitorId": "fashion-es-weekly","targets": [{"id": "example-brand","name": "Example Brand","domain": "example.com","metaPageId": "YOUR_CONFIRMED_META_PAGE_ID",}],"platforms": ["META"],"country": "ES","providerMode": "DATASET","metaSourceDataset": "YOUR_META_DATASET_ID","initialRunBehavior": "BASELINE_ONLY","includeLandingAnalysis": False,"analysisMode": "RULES","dryRun": True,}client = ApifyClient(os.environ["APIFY_TOKEN"])run = client.actor(ACTOR_ID).call(run_input=actor_input)if run is None or run.get("status") != "SUCCEEDED":status = None if run is None else run.get("status")raise RuntimeError(f"Actor run did not succeed: {status}")items = list(client.dataset(run["defaultDatasetId"]).iterate_items(clean=True))change_events = [item for item in items if item.get("recordType") == "CHANGE_EVENT"]run_summary = next((itemfor item in reversed(items)if item.get("recordType") == "RUN_SUMMARY"),None,)for event in change_events:print(event["eventId"],event["platform"],event["targetName"],event["changeType"],event["changedFields"],)print("Run summary:", run_summary)
call() starts an asynchronous Actor run on Apify and waits for completion. For serverless request handlers, start the run outside the HTTP request path and consume an ACTOR.RUN.SUCCEEDED webhook instead of holding the handler open.
API integration: Node.js
Install the official client:
$npm install apify-client
Use async iteration so large Datasets are paginated automatically:
import { ApifyClient } from 'apify-client';const ACTOR_ID = 'dekaz/cross-channel-ad-intelligence-monitor';const actorInput = {monitorId: 'fashion-es-weekly',targets: [{id: 'example-brand',name: 'Example Brand',domain: 'example.com',metaPageId: 'YOUR_CONFIRMED_META_PAGE_ID',},],platforms: ['META'],country: 'ES',providerMode: 'DATASET',metaSourceDataset: 'YOUR_META_DATASET_ID',initialRunBehavior: 'BASELINE_ONLY',includeLandingAnalysis: false,analysisMode: 'RULES',dryRun: true,};const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor(ACTOR_ID).call(actorInput);if (run.status !== 'SUCCEEDED') {throw new Error(`Actor run did not succeed: ${run.status}`);}const changeEvents = [];let runSummary = null;for await (const item of client.dataset(run.defaultDatasetId).listItems({ clean: true, chunkSize: 500 })) {if (item.recordType === 'CHANGE_EVENT') changeEvents.push(item);if (item.recordType === 'RUN_SUMMARY') runSummary = item;}for (const event of changeEvents) {console.log({eventId: event.eventId,platform: event.platform,target: event.targetName,changeType: event.changeType,changedFields: event.changedFields,});}console.log('Run summary:', runSummary);
Keep APIFY_TOKEN on a trusted backend. Never expose it in browser code, public repositories, or client-side environment variables.
For the underlying REST flow and Dataset APIs, see the official Apify API documentation and JavaScript pagination guide.
Baselines, schedules, and idempotency
dryRun: truevalidates source compatibility but does not write monitor state, send webhooks, or establish a baseline.- The first persistent run should normally use
dryRun: falsewithBASELINE_ONLY. It records observed ads without emitting every existing ad asNEW. - Later scheduled runs must reuse the same
monitorIdand stable targetidvalues. - Avoid overlapping runs for the same monitor. A distributed lease protects state, but schedules should still leave enough time for the previous run to finish.
eventIdis deterministic across retries of the same transition. Make it a unique key downstream.recordIdis deterministic for the final run summary.- A stable Apify run-derived
scanIdhelps make process restarts idempotent.
Source health and cautious inactivity detection
The summary reports one of these health states for each target/platform check:
FULL: the connector considers the configured source response complete.PARTIAL: usable results exist, but records or important fields are unavailable.BLOCKED: the source denied or rate-limited collection.TRUNCATED: pagination or a safety limit prevented a complete result.UNSUPPORTED: no compatible source or mode is available.FAILED: collection or normalization failed.
The run summary can also report two orchestration states that are not source-health claims:
SKIPPED_BUDGET: the check was not started because the remaining custom-event budget was insufficient.ALREADY_PROCESSED: the same target/platform was already completed during this stable Actor run ID, typically after a process restart.
Only FULL scans count as negative evidence when an already-observed ad disappears. Partial, blocked, truncated, unsupported, and failed scans do not advance deactivation counters. An ad first becomes POSSIBLY_INACTIVE; it reaches INACTIVE_CONFIRMED only after the configured number of consecutive complete missing scans.
Webhook alerts
Set webhookUrl to receive a JSON payload containing the run summary and persisted change events. If webhookHmacSecret is present, requests include:
X-Ad-Intelligence-Signature: sha256=<hex digest>Idempotency-Key: <stable scan ID>
Verify the signature over the exact request body before processing it. Delivery is bounded and retried only for transient network or HTTP errors. No webhook is sent in dry-run mode.
Landing-page analysis
When enabled, the Actor visits public HTTP(S) landing URLs and extracts observable page metadata, offer text, redirects, a content fingerprint, and common technology signatures. Requests block credentials in URLs, private/reserved network addresses, unsupported MIME types, excessive redirects, oversized responses, and slow responses.
Landing analysis does not submit forms, log in, solve access controls, or infer conversions. Disable it when it is outside your lawful purpose or not needed for the monitor.
Pay-per-event and source costs
The implementation uses one custom event: advertiser-channel-check. A check is chargeable only when its result is usable: either a complete verified-zero response or a response containing normalized ads. Empty partial, blocked, unsupported, and failed checks are not charged by this custom event.
Pricing is controlled by the live Pricing tab in Apify Store; the repository does not guarantee a price. Apify platform usage can also apply. Compatible child source Actors are billed separately under their own Store listings and remain subject to maxSourceChargeUsd and the run-level maximum charge you configure.
dryRun disables this Actor's custom event, but child source Actors can still charge. Use DATASET mode with existing datasets when you want to validate without starting child Actors.
Privacy, retention, and responsible use
- Source datasets are opened with read-only permission and are not modified.
- Public transparency records can contain creative text and media URLs, landing URLs, advertiser or payer names, creator identity, targeting attributes, and impression ranges. Normalized evidence can retain those fields.
- Landing-page requests disclose a normal network request to the destination site.
- Webhook inputs are marked secret. The signing secret is not written to datasets, reports, or returned delivery diagnostics.
- Persistent monitors use named Apify storage for state, normalized observations, and diagnostics. Named storage remains until you delete it; default run storage follows your Apify plan and retention settings. See Apify storage retention.
dryRunsuppresses persistent monitor-state mutations and alert delivery, but it can still read selected datasets and can still start separately billed child Actors outsideDATASETmode.- Delete the monitor's named
ad-intel-...-state,ad-intel-...-ads, andad-intel-...-diagnosticsstorages when you no longer need the history. - You are responsible for a lawful basis, data-protection obligations, platform terms, source-Actor terms, and access restrictions. Public availability does not grant copyright or redistribution rights over third-party creatives.
This tool supports transparency and competitive research. It is not legal advice and is not a substitute for a DSA, GDPR, copyright, or platform-terms assessment.
Beta checklist before relying on a monitor
- Confirm that each target maps to the correct advertiser using stable platform identifiers.
- Inspect source-health warnings and skipped-record counts after every schema or provider change.
- Compare a sample of normalized records with the original transparency-library pages.
- Keep
BASELINE_ONLYfor the first persistent run unless you intentionally want existing ads emitted as new. - Treat a zero-result scan as meaningful only when health is
FULL. - Pin and retest compatible source Actor versions before production schedules.
- Deduplicate downstream events by
eventIdand summaries byrecordId. - Monitor source costs and the run-level maximum charge.
If a run looks incomplete, inspect the final RUN_SUMMARY and SUMMARY.json before acting. They are designed to make missing coverage visible rather than hide it behind an empty result.