adlibrary.com Logoadlibrary.com
Share
Competitive Research,  Guides & Tutorials

Automate Client Competitor Ad Reports: The Section That Builds Itself

Automate the competitor ad section of client reports: watchlists, scheduled curate-and-diff, AI enrichment, and Slides or PDF output at ~10 credits/client.

Automate Client Competitor Ad Reports: The Section That Builds Itself

Automate client competitor ad reports and you delete the single most resented task in agency account management: the last-Friday-of-the-month scramble where someone opens Meta's Ad Library, screenshots fourteen ads, pastes them into a deck, and writes "competitor activity remains strong" under each one. Every account lead knows this ritual. Most have done it after 9pm.

TL;DR: The competitor slide is the most-read and worst-produced part of every client report. It is also the most automatable, because it asks the same four questions about the same brands every month: what's new, what formats, how active, which creative matters. This guide builds the full pipeline on the AdLibrary API: a per-client watchlist of saved advertisers, a scheduled curate-and-diff job, AI enrichment for the standout creative, and rendering to Sheets, Slides, or Markdown-to-PDF. Roughly 10 credits per client per month on the Business plan (€329/mo, 1000+ credits, API access).

The fix is not a better template. The fix is a pipeline. Same brands, same questions, every month: that is the textbook profile of work a script should own.

Why automate client competitor ad reports at all

Open any client-facing deck and watch where the scroll stops. Performance tables get skimmed. Budget pacing gets nodded at. The competitor section gets read, forwarded internally, and screenshotted for the CMO. Agency client reporting lives or dies on perceived insight, and nothing signals insight like "here is what your rival shipped this month."

Now look at how that section gets made. An account lead with a €120/hr fully loaded cost spends 60 to 90 minutes per client manually browsing ad libraries across platforms. Ten clients means two full working days per month spent on screenshots. The output is stale the day it ships, and the coverage is whatever one tired human happened to scroll past.

That mismatch is the opportunity. The most-read section is produced by the least repeatable process in the agency. Manual research makes sense for a one-off pitch teardown. It makes no sense for a recurring deliverable with a fixed spec, which is exactly what the competitor section is. The case to automate client competitor ad reports rests on that repetition: the whole job decomposes into a watchlist, a scheduled pull, a diff, and a render step. Each piece is boring on its own. Chained together, they produce the section while you sleep.

One caveat before the build: this pipeline replaces the collection work, never the judgment. The two sentences of analysis you add to the data are the part the client pays for. More on that in the white-label section.

The monthly competitor report spec

Before writing a line of code, lock the spec. A competitor section that tries to say everything says nothing, and an automated one will drift into noise unless the questions are fixed. After enough client calls, the questions converge on four:

1. What's new. Which ads appeared since the last report, per competitor. This is the headline number. New-ad count also doubles as a creative refresh cadence signal: a competitor shipping 40 new ads a month is running a testing program, while one shipping 2 is coasting.

2. What formats. The image/video/carousel mix, and how it shifted. A competitor moving from static to video at scale is telegraphing where their money is going next quarter.

3. Estimated activity. How many ads are live, impression bands, and estimated spend direction. Two honesty rules belong in the report footer: impressions come as bucketed ranges, never exact counts, and spend is always an estimate, never a number from the advertiser's account. For the deeper version, the competitive spending report guide covers framing estimated spend without overclaiming.

4. Notable creative. One to three ads worth a paragraph each: the longest-running ad (longevity is the strongest performance proxy in public ad data, because losers get killed in week one), plus anything that signals a new angle, offer, or landing page.

That's the whole spec. New ads, formats, activity, notable creative, per competitor, once a month. Resist the urge to add more, because every extra metric dilutes the four the client actually reads. Write it down as a one-page SOP so every client gets the identical structure.

Step 0: pick a data source that covers more than Meta

The pipeline is only as good as what it pulls from. Meta's Ad Library API is the originator here and deserves credit: free, official, and exactly right if your client's competitors advertise only on Facebook and Instagram. But it returns political and social-issue ads for most of the world, full commercial coverage only for EU/UK ads, and it requires app review plus access tokens that expire every 60 days. Google publishes its own Ads Transparency Center, LinkedIn exposes an Ad Library API of its own, and TikTok runs a separate Commercial Content Library. Four platforms, four auth schemes, four response shapes. Teams that try to stitch these together usually end up in the graveyard described in our scraping tools roundup.

Meta's free API is fine for one platform. The moment a client asks why their competitor's TikTok and YouTube ads aren't in the report, you need something else. This guide uses the AdLibrary API, a paid power-user layer on top of that landscape: one adl_ key, eleven platforms (multi-platform coverage spanning Facebook, Instagram, TikTok, YouTube, Google, LinkedIn and more), and per-ad signals the free sources don't carry, including engagement counts, impression bands, estimated spend, and a heat score. No app review, no token refresh dance. Full endpoint details live in the API documentation and implementation guide.

Step 1: build the per-client watchlist with saved advertisers

A competitor report starts with an unambiguous answer to "who are we watching for this client?" Brand names are ambiguous. Platform IDs are not. The watchlist step converts each competitor name into platform IDs once, then stores them server-side so every later pull is a single call.

Resolution is free. The advertisers/search endpoint fans one query out across Meta, Google, and LinkedIn and tells you when the platforms agree it's the same brand:

bash
curl -G "https://adlibrary.com/api/advertisers/search" \
  -H "Authorization: Bearer $ADLIBRARY_API_KEY" \
  --data-urlencode "q=Huel" \
  --data-urlencode "country=US"

The response carries a best_match with the Meta page ID, the Google AR… advertiser ID, and the LinkedIn company ID, plus per-platform candidate lists. One competitor is rarely one account: a sportswear brand might run a main page, a regional page, and a sub-brand page. Save them all under one advertiser:

bash
curl -X POST "https://adlibrary.com/api/advertisers" \
  -H "Authorization: Bearer $ADLIBRARY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Huel",
    "domain": "huel.com",
    "meta_page_ids": ["161569261234567"],
    "google_advertiser_ids": ["AR12345678901234567890"],
    "linkedin_company_ids": ["2738123"]
  }'

Saving is free and returns a UUID. That UUID is the competitor from now on. For an agency, the natural structure is a small config file mapping each client to a list of saved advertiser UUIDs, one entry per competitor.

Do this onboarding step once per client, by hand, with the account lead in the room. Watchlist quality is a judgment call (who actually counts as a competitor?) and it is the one place where 20 minutes of human attention pays off for every automated month after. The same watchlist feeds the competitor ad monitoring alerts described in our diff-detection playbook.

Step 2: schedule the pull with curate

With the watchlist saved, the monthly pull is one endpoint per competitor. curate fans out across every platform ID stored on the saved advertiser, merges the results, and dedupes them by ad_key:

python
import requests

KEY = "adl_your_key_here"
HEAD = {"Authorization": f"Bearer {KEY}"}
BASE = "https://adlibrary.com/api"

def pull_competitor(advertiser_uuid: str) -> list[dict]:
    """One curate session: every recent ad, all platforms, deduped."""
    r = requests.post(f"{BASE}/advertisers/{advertiser_uuid}/curate",
                      headers=HEAD, json={})
    r.raise_for_status()
    data = r.json()
    ads = []
    for platform in ("meta", "google", "linkedin"):
        block = data.get(platform) or {}
        ads.extend(block.get("ads", []))
    return ads

The billing model fits reporting unusually well. A curate session costs 1 credit and stays open for 30 minutes, and paginating deeper within that window is free: pass the cursors object from the previous response back into the next call until the cursors come back null. One competitor's full recent portfolio costs 1 credit, however many pages it spans.

Scheduling is whatever your stack already runs. A cron line on any server covers it:

cron
0 6 1 * * /usr/bin/python3 /opt/reports/competitor_pull.py >> /var/log/competitor_pull.log 2>&1

The 6am-on-the-1st pattern gives you a clean calendar-month window and a finished draft hours before anyone opens their laptop. Prefer visual builders? The same pull works as an HTTP module in the no-code tools. Our n8n recipes, Make.com recipes, and Zapier recipes all include scheduled competitor pulls you can adapt. Mind the rate limit either way: 10 requests per minute per key, so a roster of 30 competitors needs modest pacing, not parallel blasts.

Two operational notes. Use separate API keys per client or environment (an account holds up to 10), so a rotated key never disrupts the whole roster. And if a second job hits the same advertiser while a curate is in flight, the API returns a 409 with a retry window, so serialize per advertiser.

Step 3: diff the month and compute the findings

Raw ads are not a report. The report is the delta: what exists now that didn't exist last month. That requires one piece of state, a stored set of every ad_key you've seen per competitor. ad_key is the stable, platform-prefixed identifier (meta_…, google_…, linkedin_…), which makes the diff a set subtraction:

python
import json, pathlib

STATE = pathlib.Path("state")
STATE.mkdir(exist_ok=True)

def diff_new_ads(competitor: str, ads: list[dict]) -> list[dict]:
    """Return ads never seen before; persist the union for next month."""
    f = STATE / f"{competitor}.json"
    seen = set(json.loads(f.read_text())) if f.exists() else set()
    current = {ad["ad_key"] for ad in ads}
    new_keys = current - seen
    f.write_text(json.dumps(sorted(seen | current)))
    return [ad for ad in ads if ad["ad_key"] in new_keys]

The first run seeds the baseline and reports nothing new, which is correct behavior. From month two onward, new_ads is finding number one, computed instead of eyeballed.

Findings two and three fall out of fields already on each ad. ads_type encodes the format (1 image, 2 video, 3 carousel, 4 collection), days_count is runtime, and the engagement counters ride along:

python
FORMATS = {1: "image", 2: "video", 3: "carousel", 4: "collection"}

def format_mix(ads: list[dict]) -> dict[str, int]:
    mix: dict[str, int] = {}
    for ad in ads:
        label = FORMATS.get(ad.get("ads_type"), "other")
        mix[label] = mix.get(label, 0) + 1
    return mix

def notable(ads: list[dict], n: int = 3) -> list[dict]:
    """Longest-running creatives: the strongest public performance proxy."""
    return sorted(ads, key=lambda a: a.get("days_count", 0), reverse=True)[:n]

Store last month's format_mix next to the seen-keys file and the month-over-month shift becomes a subtraction too ("video share up from 38% to 55%"). For activity, count live ads and roll impression bands into a direction, up or down. This is competitive intelligence in its most defensible form: every claim traces to a field in a JSON response, and a skeptical client can verify any specific ad in Meta's public library.

AI enrichment of competitor ad creative showing structured analysis insights for automate client competitor ad reports workflow

Step 4: enrich only the notable creative

Counts tell the client that something changed. The notable-creative paragraphs tell them what it means, and writing those by hand is the second-biggest time sink after collection. The enrichment endpoint does the heavy half: send it one ad and it returns a structured teardown with a transcript, the strategic read (product, funnel stage, target), and the persuasion analysis of the hook and offer.

python
def enrich(ad: dict) -> str:
    payload = {"ad": {
        "ad_key": ad["ad_key"],
        "platform": ad.get("platform", "facebook"),
        "advertiser_name": ad.get("advertiser_name"),
        "message": ad.get("message"),
        "video_url": ad.get("video_url"),
        "preview_img_url": ad.get("preview_img_url"),
    }}
    r = requests.post(f"{BASE}/enrichment", headers=HEAD, json=payload)
    r.raise_for_status()
    return r.json()["enrichment"]["summary"]

Discipline matters here, for cost and for signal. Enrich the two or three ads per competitor that clear your bar (top runtime, or a new landing page, or a format the brand hasn't used before), never the whole pull. Each analysis costs 1 credit for text and standard-length video, and an ad you've already unlocked returns free on repeat calls, so re-renders cost nothing. The summary drops into the report as the draft of the "notable creative" paragraph. The account lead edits it into the client's context instead of writing from a blank page. The full teardowns also feed the creative brief workflow when a client wants to answer a competitor's angle, and the strongest finds belong in the team swipe file.

Step 5: render the report in Sheets, Slides, or Markdown

The render step depends on what your clients already receive. Three patterns cover almost every agency.

Markdown to PDF is the lowest-friction start. The script writes plain Markdown and pandoc converts it with your letterhead template:

python
def render_markdown(client: str, sections: list[dict]) -> str:
    lines = [f"# Competitor ad activity: {client}", ""]
    for s in sections:
        lines += [
            f"## {s['name']}",
            f"- **New ads this month:** {len(s['new_ads'])}",
            f"- **Format mix:** {s['mix']}",
            f"- **Live ads:** {s['live_count']} ({s['trend']})",
            "", s["notable_summary"], "",
        ]
    return "\n".join(lines)
bash
pandoc report.md --template=agency.latex -o acme_june.pdf

Google Sheets suits clients who want a living dashboard. Append one row per competitor per month via the Sheets API and let charts accumulate history. Sheets is also the natural seam if a junior reviews numbers before the deck goes out.

Google Slides automates the deck itself. Keep a branded template with placeholder tokens ({{COMPETITOR_1_NEW_ADS}}, {{NOTABLE_SUMMARY_1}}), copy it monthly, and fill it with one batchUpdate of replaceAllText requests through the Slides API. The output matches the deck your team used to assemble by hand, because it uses the same template.

Whichever renderer you choose, keep the pipeline stages separate (pull, diff, enrich, render), so you can swap the output format without touching the data logic. Prefer driving the flow conversationally? The same endpoints work from Claude Code, as a marketing-ops automation, or behind a self-built MCP server, and agency teams on MCP already use these recipes.

White-label the section without faking the work

A recurring agency worry: is it honest to present API-collected data as our work? Yes. The data is an ingredient, like the ad platform's own reporting or your analytics stack. Nobody captions their dashboard "powered by a CSV export." What the client buys is the watchlist judgment, the spec, the analysis paragraphs, and the recommendation that follows from them.

Practical white-label rules that hold up:

  • Brand the deliverable, not the plumbing. Your template, your logo, your narrative voice. A methodology line ("compiled from public ad libraries across 11 platforms, including Meta's Ad Library") is both honest and more credible than a tool name.
  • Keep the honesty footnotes. Impressions as ranges, spend as estimates. One overclaimed number a client disproves kills the section's credibility for a year.
  • Add one human sentence per competitor. "They moved to video and their new ads all push the subscription offer, so we should test a bundle counter-angle this quarter." That sentence is the agency. It cannot be automated and it is what separates your report from a data dump. Watch for ad fatigue patterns in competitors' long-runners too, since a rival's tired creative is a pitch for fresh work.
  • Scope keys per client. Separate API keys per account mean clean usage attribution and clean offboarding when a client leaves.

The same pipeline moonlights in new business. Point it at a prospect's competitors the night before a meeting and walk in with the exact section you're proposing to deliver monthly, populated with their market. Teams using this for pitch preparation report it lands harder than any credentials slide.

Price it as a productized service

Here is where automation changes the business model instead of merely saving hours. A manually built competitor section is cost-plus work: hours in, margin on top, scaling linearly with headcount. An automated section is a productized service: fixed spec, fixed price, near-zero marginal cost.

Price on value, anchored by alternatives. The client's alternative is an analyst spending a day a month doing worse coverage by hand, or an enterprise intelligence platform at four figures monthly. A competitor-intelligence add-on at €300 to €800 per client per month sits comfortably under both anchors, and your marginal cost per client is roughly 10 API credits plus 20 minutes of account-lead editing. Run the numbers through the ROAS calculator logic in reverse: at €500/mo across ten clients, the section grosses €60k a year against a few hours of monthly editing.

Three packaging rules from agencies that have done this:

  1. Sell it as a named product, separate on the invoice. "Competitor Ad Intelligence: monthly" reads as a deliverable. Buried in the retainer, it reads as free and gets valued accordingly.
  2. Tier by depth, never by effort. Standard tier: four competitors, the four findings. Plus tier: eight competitors, enriched teardowns of every notable ad, a quarterly trend review. Same pipeline, different config values.
  3. Use month one as the wedge. The baseline month produces no diff, so frame it as the "market scan" deliverable, a full portfolio snapshot per competitor. It justifies a setup fee and seeds the state files at the same time.

The deeper effect is positioning. An agency that shows up monthly with verifiable market intelligence is consulting on strategy, and strategy retainers survive the budget reviews that media-execution retainers don't.

The credit math: what it costs to run

Productized pricing needs a known cost floor, so count the credits for a typical setup: one client, six competitors, monthly cadence.

Pipeline stepAPI callCredits
Watchlist resolution (onboarding)advertisers/search0 (free)
Watchlist save (onboarding)POST /advertisers0 (free)
Monthly pull, 6 competitorscurate, one session each6
Pagination within each sessioncursor continuation0 (free)
Enrichment, ~3 notable adsPOST /enrichment3
Balance check for the log linecredits0 (free)
Total per client per month~9–10

Call it 10 credits per client per month. A 20-client roster runs about 200 credits, well inside the Business plan's 1000+ monthly allowance at €329/mo, which is also the plan that includes API access in the first place. The headroom funds ad-hoc work: keyword searches for pitches at 1 credit per page (each search response includes a total count, useful for sizing a market), or a 10-credit winners scan when a client wants a deep teardown of one rival's portfolio. Every billed response returns your remaining balance, so the monthly job can log it and alert you long before it matters. The ad spend estimator gives you a sanity check on competitor budgets, and the CPM calculator helps translate impression bands into rough ranges for the client conversation.

Failed calls auto-refund, so error months don't eat the margin. The cost floor per client is a few euros in credits against a €300 to €800 deliverable price. Software margins, agency relationship.

Frequently Asked Questions

How do I automate the competitor ad section of client reports?

Fix a four-part spec (new ads, format mix, estimated activity, notable creative), save each client's competitors as watchlist entries with their platform IDs, then schedule a monthly job that pulls every competitor's recent ads via the AdLibrary API's curate endpoint, diffs the returned ad_key set against last month's stored state, enriches the top two or three creatives, and renders the findings into your existing template via Google Sheets, Slides, or Markdown-to-PDF.

How many credits does an automated competitor report cost per client?

Roughly 10 credits per client per month for a six-competitor watchlist: one curate session per competitor (1 credit each, with free cursor pagination inside the 30-minute session) plus about three AI enrichments at 1 credit each. Brand resolution, saving advertisers, and balance checks are free. A 20-client roster runs about 200 credits, inside the Business plan's 1000+ monthly allowance.

Can I white-label competitor ad data in client reports?

Yes. The API returns raw JSON with no branding, so the deliverable carries your template, logo, and narrative. The honest framing is a methodology note such as "compiled from public ad libraries across 11 platforms" rather than a vendor name, plus footnotes stating that impressions are bucketed ranges and spend figures are estimates.

What should a monthly competitor ad report include?

Four findings per competitor: new ads since the last report, the image/video/carousel format mix and its month-over-month shift, estimated activity (live ad count, impression bands, spend direction), and one to three notable creatives with a short analysis of the hook, offer, and landing page. Anything beyond that dilutes the section clients actually read.

Why use the AdLibrary API instead of Meta's free Ad Library API for client reporting?

Meta's Ad Library API is free and official, but it covers Meta only, returns commercial ads only for EU/UK regions (political and social-issue ads elsewhere), and requires app review plus tokens that expire every 60 days. The AdLibrary API is a paid upgrade that adds ten more platforms, per-ad performance signals like impression bands, estimated spend, and heat score, and a single adl_ key with no review process.

Automate client competitor ad reports starting this month

The competitor section earns more client attention than anything else in the deck, and until now it cost more human hours than anything else too. The pipeline above ends that trade. Lock the four-question spec, save each client's watchlist once, let cron run the curate-and-diff on the 1st, enrich the standouts, render into your own template, and spend the recovered hours on the one thing the script can't do: telling the client what it means. To automate client competitor ad reports across a whole roster, the only inputs are a fixed spec and an API key.

Month one is the baseline scan. Month two is the first diff. By month three the section builds itself and your account leads have forgotten the screenshot ritual existed. Start on the Business plan (€329/mo, 1000+ credits, full API access, integration help included), wire up the first client this week, and ship the section on the 1st.

Related Articles

AdLibrary image
Competitive Research,  Guides & Tutorials

Competitive Spending Report: Build Yours in 2026

A step-by-step guide to building a competitive spending report — what data to collect, which sources to use, how often to run it, and how to turn signals into budget decisions.