# Competitor Ad Strategy Brief — Offers, Messaging & Changes (`elfajad/competitor-ad-strategy-brief`) Actor

Turn supplied Meta ad JSON or selected Apify datasets into a brand research report: observed offers, messaging, CTAs, destination URLs, sample changes and test hypotheses. Printable HTML + JSON. Free fictional demo. No AI key.

- **URL**: https://apify.com/elfajad/competitor-ad-strategy-brief.md
- **Developed by:** [El Fajad](https://apify.com/elfajad) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $15.00 / brand strategy brief

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?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Competitor Ad Strategy Brief

Turn **one advertiser's supplied ad records** into a structured research brief: recurring offers and messaging, calls to action, creative formats, landing-page destinations, differences against a previous sample, and campaign ideas to test. Get a **printable HTML report, traceable source evidence and JSON** for agency, CRM and AI workflows.

This is an **analysis Actor**. Bring an existing ad dataset or paste JSON. It does not collect ads, run another paid Actor, fetch landing pages, download creatives or require an AI API key.

### Try the fictional demo first

```json
{"mode":"demo"}
```

Demo always uses the fixed fictional **Demo Supply** examples. It ignores custom ad records and does **not** trigger the paid strategy-brief event. Any platform startup event shown in Pricing still applies. Demo output and HTML are clearly marked fictional.

Open **Brand brief → Open printable report**. Print the HTML page to save a PDF. The demo includes a current/previous comparison so you can inspect the result before supplying your data.

### Analyze an Apify ad dataset

```json
{
  "mode": "dataset",
  "brandName": "Your competitor",
  "currentDatasetId": "YOUR_17_CHAR_DATASET_ID",
  "previousDatasetId": "OPTIONAL_PRIOR_DATASET_ID",
  "observedAt": "2026-09-28T12:00:00Z",
  "previousObservedAt": "2026-09-21T12:00:00Z"
}
```

1. Collect/export ads using your existing workflow. For example, [Apify's Facebook Ads Scraper](https://apify.com/apify/facebook-ads-scraper) returns compatible Meta snapshot fields. Its collection fees are separate and are not included in this Actor's report price.
2. Select **Select Apify ad datasets** as the input mode. Use the dataset picker to select the current and optional prior datasets.
3. Supply a label and, if known, collection timestamps. Source rows must describe **one advertiser**; mixed advertisers are rejected.
4. Set **maximum charge to at least $15.01** for a real report, then run. Default demo spending settings may be too low for paid mode.
5. Open the printable report or use the output JSON.

The Actor has Limited permissions. The storage pickers grant **READ only** to the datasets you explicitly select. It does not modify source data. In API calls, pass the source IDs in the declared input fields so Apify can apply the same scoped storage permissions.

The first **100 dataset rows** are analyzed. If the source is larger, the report explicitly warns about sampling. Filter your upstream collection to a single advertiser before running; this Actor does not search an entire multi-brand dataset for a matching name.

### Paste ad JSON instead

```json
{
  "mode": "inline",
  "brandName": "Example brand",
  "currentAds": [
    {"id":"sample-1","advertiserName":"Example brand","body":"Save 20% on our starter kit.","cta":"Shop now","landingPageUrl":"https://example.com/kits","format":"IMAGE","isActive":true,"startDate":"2026-09-01"},
    {"id":"sample-2","advertiserName":"Example brand","body":"Free shipping on your first order.","cta":"Shop now","landingPageUrl":"https://example.com/kits","format":"VIDEO","isActive":true,"startDate":"2026-09-10"},
    {"id":"sample-3","advertiserName":"Example brand","body":"See our simple everyday routine.","cta":"Learn more","landingPageUrl":"https://example.com/how-it-works","format":"VIDEO","isActive":true,"startDate":"2026-09-15"}
  ]
}
```

These are synthetic format examples, not real advertiser evidence. Inline mode requires **3–100 distinct ads with usable copy**. Optional `previousAds` accepts an earlier array for the same advertiser. Download **Normalized ad snapshot** from a prior run and reuse that array as `previousAds`.

Supported fields include the normalized example above and common Meta export fields: `adArchiveId` / `adArchiveID` / `ad_archive_id`, `pageId` / `pageID`, `snapshot.pageName`, `snapshot.body.text`, `snapshot.cards`, `snapshot.ctaText`, `snapshot.linkUrl`, `snapshot.displayFormat`, `startDateFormatted`, `isActive` and publisher-platform arrays. Media-only rows without usable copy are excluded. Count-only and error rows are not treated as ads.

### What you receive

| Output | What it establishes |
|---|---|
| Offers and messaging | Selected English/Arabic phrases observed in ad copy, with counts, sample shares and source evidence |
| CTAs, formats and platforms | Distribution of supplied metadata; missing values remain Unknown |
| Destination URLs | Supplied URLs grouped after removing common tracking parameters; pages were not visited |
| Repeated copy | Exact normalized copy across distinct ad IDs; duplicate rows for the same ID are merged |
| Observed ad age | Supplied start date to observation reference for ads explicitly marked active; no success inference |
| Previous-sample comparison | New to sample, not seen in current sample, creative record changes and explicit status changes |
| Test hypotheses | Suggested experiments based on observed phrases; measure results in your own campaigns |
| Printable HTML | Reviewable report with coverage, exclusions and evidence links |

Source links use supplied public HTTP(S) URLs or a Meta Ad Library link derived from a supplied numeric archive ID. The Actor does not verify that these links are live. For missing IDs, copy fingerprints and input row numbers provide provenance; text changes can then appear as new/missing records.

No “winning ad,” competitor ROAS, spend, conversion, profit or revenue estimate is generated. A long observed age does not prove uninterrupted delivery. An ad absent from your current sample may still be running.

### Inputs and limits

| Field | Use |
|---|---|
| `mode` | `demo` (default), `inline` or `dataset` |
| `brandName` | Required for real reports; up to 120 characters |
| `currentAds`, `previousAds` | Inline JSON arrays, maximum 100 rows each |
| `currentDatasetId`, `previousDatasetId` | Selected read-only datasets for dataset mode |
| `observedAt`, `previousObservedAt` | Optional non-future ISO dates; previous must precede current when both supplied |
| `reportLabel` | Optional printable report heading, up to 120 characters |

Maximum input size is 5 MiB. Trim unused large creative/detail fields from inline input. At least three distinct usable ads are needed for a paid report. Missing advertiser identity is reported as an unverified label; known conflicting advertisers are rejected.

`observedAt` is supplied by you. When omitted, analysis time is used as the age reference and source freshness remains unknown. This Actor does not automatically maintain cross-run history: select a previous dataset or supply `previousAds` for comparisons.

### Pricing and billing

Launch report price: **$15 per delivered brand brief**, event `strategy-brief`, covering up to 100 current and 100 previous rows. The **Pricing tab is authoritative**. Collection by other Actors is separate.

- Fixed fictional demo: no strategy-brief event.
- Real usable report: one strategy-brief event, including when no configured theme is found or no previous snapshot was supplied.
- Empty/unusable input, fewer than three distinct usable ads, mixed advertisers or insufficient budget: no report event.
- Startup event: expected **$0.00005** per run at supported memory sizes, including demos and failed input runs.
- No separate dataset-row charge or platform-usage pass-through is intended.

The HTML report is saved before its dataset row is charged. The Actor checks the report-event budget first and skips already-delivered results if the same run is resurrected. Set $15.01 as your run cap for one real report; a cap is not a minimum charge. Plan restrictions may prevent real reports when your available run budget is lower. The fictional demo remains available with a small cap.

### Output records

The default dataset contains one brief with `brandName`, `isDemo`, `analysisId`, `generatedAt`, `observedAt`, counts, `summary`, `themes`, `repeatedCopy`, `ctas`, `formats`, `platforms`, `landingPages`, `longevity`, `comparison`, `testHypotheses`, normalized `ads`, `coverage`, `reportUrl` and `reportKey`.

Default key-value store:

- `brief-<analysisId>.html`: printable HTML.
- `ADS-SNAPSHOT`: normalized ad array for reuse as inline `previousAds`.
- `RUN-SUMMARY`: delivery/demo/budget status.

Download important output before Apify retention expires.

### API and MCP

Use `elfajad/competitor-ad-strategy-brief` through the official Apify client, API or MCP server after publication. Input, output and dataset schemas describe the workflow.

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({token: process.env.APIFY_TOKEN});
const run = await client.actor('elfajad/competitor-ad-strategy-brief').call({
  mode: 'dataset', brandName: 'Your competitor',
  currentDatasetId: process.env.SOURCE_DATASET_ID
}, {maxTotalChargeUsd: 15.01, memory: 256});
const {items} = await client.dataset(run.defaultDatasetId).listItems();
```

### Development and support

Node.js 22+, [Apify SDK](https://github.com/apify/apify-sdk-js) (Apache-2.0), [ipaddr.js](https://github.com/whitequark/ipaddr.js) (MIT). Deterministic analysis; no proprietary model or paid enrichment provider. No VPS.

```sh
npm ci
npm test
npm run sample
```

Use the Issues tab for reproducible problems. Include the run ID and non-sensitive field names; do not publish private ad datasets, tokens or payout details.

# Actor input Schema

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

Demo always uses fixed fictional data and does not trigger the $15 report event. Select inline or dataset for your own data.

## `brandName` (type: `string`):

Required for inline/dataset mode. Used as the report label. Rows must describe only one advertiser.

## `currentAds` (type: `array`):

Inline mode: paste an array exported from an ad scraper, or normalized objects with id, advertiserName, body, cta, landingPageUrl, format, isActive and startDate. Maximum 100 rows.

## `previousAds` (type: `array`):

Inline mode: same advertiser's previous sample. Stable IDs allow exact record comparisons. Missing ads are labeled not seen, not stopped.

## `currentDatasetId` (type: `string`):

Dataset mode: explicitly select one dataset containing only this advertiser's ads. Reads at most 101 rows, analyzes the first 100. Source is read-only.

## `previousDatasetId` (type: `string`):

Dataset mode: select an earlier ad dataset for the same advertiser. No source writes.

## `observedAt` (type: `string`):

ISO date/time of collection, e.g. 2026-09-28T12:00:00Z. If omitted, analysis time is used and source freshness remains unknown.

## `previousObservedAt` (type: `string`):

ISO date/time of the earlier sample. Must precede current observedAt if both are provided.

## `reportLabel` (type: `string`):

Optional heading on the printable report.

## Actor input object example

```json
{
  "mode": "demo"
}
```

# Actor output Schema

## `brief` (type: `string`):

One brand report with observed patterns, source evidence, test hypotheses and printable HTML URL.

## `snapshot` (type: `string`):

Download this array to reuse as previousAds in inline mode.

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

Delivery count and fictional-demo/budget status.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("elfajad/competitor-ad-strategy-brief").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("elfajad/competitor-ad-strategy-brief").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 '{}' |
apify call elfajad/competitor-ad-strategy-brief --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,elfajad/competitor-ad-strategy-brief"
        }
    }
}
```

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/EcA5kYJgp7gMaG5GP/builds/ewfDcIxmuE6yiiqay/openapi.json
