# Ad library aggregation (/docs/recipes/ads-library-aggregation) Ad library aggregation [#ad-library-aggregation] Build a competitive-intelligence dashboard that shows every ad a target company is running across Meta, Google, and LinkedIn — for sales-call prep, market-research reports, or buying-team intake. How do you find all the ads a company is running? [#how-do-you-find-all-the-ads-a-company-is-running] Query the three public ad libraries — Meta, Google Ad Transparency, and LinkedIn — in parallel through one API key. Each network has a search endpoint that takes the brand name (or domain, for Google), and the three 5-credit calls complete in one `Promise.all`, returning every active and recent ad for 15 credits total. The problem [#the-problem] Every major ad network publishes a transparency library, but each lives behind a different UI, a different query model, and a different response shape. Manually checking three libraries per competitor doesn't scale past your first sales call, and none of the official UIs export data. The solution [#the-solution] Three ad-library endpoints, all advanced tier (5 credits each): * `GET /v1/facebook/adlibrary/search/ads` — Meta Ad Library keyword search (param: `query`) * `GET /v1/google/company/ads` — Google Ad Transparency Library by advertiser (param: `domain` or `advertiser_id`) * `GET /v1/linkedin/ads/search` — LinkedIn Ad Library search (params: `keyword` and/or `company`, plus `countries`, `startDate`, `endDate`) Note that the three networks do **not** share a parameter name. Meta takes `query`, Google takes `domain` (or `advertiser_id`), and LinkedIn takes `keyword` — passing `query` to LinkedIn is silently ignored and returns an unfiltered page rather than an error. Reddit's ad library is not part of this fan-out. Both Reddit ad endpoints — the `reddit/ads/search` keyword search and the `reddit/ad` single-ad lookup — are soft-disabled: the ScrapeCreators upstream returned consecutive `502` s on 2026-06-06, so calling either now returns `503` at no charge. They stay registered pending a re-source. If you already have one of them wired up, drop the leg rather than retrying it, and treat Reddit paid coverage as a gap for now. The three ad-library responses have different upstream shapes; this recipe flattens them into a unified `{ network, ad }` row so you can render them in one table. ```typescript // recipe-ad-audit.ts // Pulls every ad a brand is running across 3 networks in parallel. // Run with: SOCIALCRAWL_KEY=sc_... npx tsx recipe-ad-audit.ts const KEY = process.env.SOCIALCRAWL_KEY; if (!KEY) throw new Error("Set SOCIALCRAWL_KEY"); const BASE = "https://www.socialcrawl.dev/v1"; const brand = "stripe"; async function get(path: string, params: Record) { const url = new URL(`${BASE}/${path}`); for (const [k, v] of Object.entries(params)) url.searchParams.set(k, v); const res = await fetch(url, { headers: { "x-api-key": KEY! } }); if (!res.ok) return { success: false, error: await res.text() }; return (await res.json()) as { success: boolean; data?: { items?: unknown[] }; credits_remaining: number; }; } type AdRow = { network: string; ad: unknown }; // Google ad-library searches by domain or advertiser_id — not free-text. // Brand → domain is the most common shape; map your input accordingly. const brandDomain = "stripe.com"; // Three networks, three parameter names. LinkedIn ignores `query`. const [meta, google, linkedin] = await Promise.all([ get("facebook/adlibrary/search/ads", { query: brand }), get("google/company/ads", { domain: brandDomain }), get("linkedin/ads/search", { keyword: brand }), ]); const ads: AdRow[] = [ ...(meta.data?.items ?? []).map((ad) => ({ network: "meta", ad })), ...(google.data?.items ?? []).map((ad) => ({ network: "google", ad })), ...(linkedin.data?.items ?? []).map((ad) => ({ network: "linkedin", ad })), ]; console.log(`${ads.length} ads found for "${brand}"`); const counts = ads.reduce>( (acc, row) => ({ ...acc, [row.network]: (acc[row.network] ?? 0) + 1 }), {}, ); console.table(counts); console.log(`credits left: ${linkedin.credits_remaining}`); ``` What you get back [#what-you-get-back] ```jsonc // Final aggregated shape after the flattening loop: [ { "network": "meta", "ad": { "id": "ad_1234567890", "page_name": "Stripe", "ad_creative_body": "Accept payments online in minutes...", "first_active": "2026-05-01", // <-- normalised from raw upstream "active": true, }, }, { "network": "linkedin", "ad": { "id": "ln_987654321", "advertiser_name": "Stripe", "headline": "Built for fast-growing teams", "creative_url": "https://www.linkedin.com/ads/...", }, }, // ... google rows mixed in ] ``` Credits cost [#credits-cost] > **Cost per run:** 15 credits per brand audit (3 networks × 5 credits). Auditing a 25-company competitive set costs 375 credits. Take it further [#take-it-further] * See [Endpoint pricing](/docs/endpoint-pricing.md) for why ad-library endpoints are advanced tier (they hit slower upstream APIs that return larger payloads). * Swap `brand = "stripe"` for any company name — the Meta and LinkedIn endpoints both accept free-text queries; Google keys off the advertiser's domain. * Widen the Meta leg: `GET /v1/facebook/adlibrary/search/companies` (5 credits) resolves a brand name to its advertiser pages, `GET /v1/facebook/adlibrary/company/ads` (5 credits) pulls one page's full run, and `GET /v1/facebook/adlibrary/ad/transcript` (10 credits) returns the spoken script of a video ad. * Google's side has two more doors: `GET /v1/google/adlibrary/advertisers/search` (5 credits) turns a brand name into an `advertiser_id`, and `GET /v1/google/ad` (5 credits) pulls a single creative by URL. * Next: [Creator engagement scoring](/docs/recipes/creator-engagement-scoring.md) shows the same parallel-fan-out pattern applied to profile data. * Tracking the competitor's organic side too? See [Competitor tracking](/docs/recipes/competitor-tracking.md). Platform references: [Facebook API](/platforms/facebook), [LinkedIn API](/platforms/linkedin).