search_ads

Search the shared competitor ad-library corpus with structured filters and/or a free-text semantic query.

Searches the AdCrunch Library — a shared, deduplicated corpus of competitor ads crawled from public ad-transparency libraries (Meta Ad Library, TikTok Commercial Content Library) and enriched at ingest with an AI Analysis (visual description, hook, format, tone, themes) plus a text embedding.

Unlike the owned-account tools (list_advertisers, list_ads, …), the library corpus is global across orgs — the ads are public disclosures, so every authenticated caller sees the same corpus. Per-org tracking (which brands you follow) drives crawling but does not constrain what you can search.

Input

FieldTypeRequiredDescription
qstringnoFree-text semantic query. When present, switches to vector search; sort and offset are ignored.
provider'meta' | 'tiktok' | 'x' | 'snapchat'noRestrict to a single ad library.
countrystring (ISO-3166-1 alpha-2)noe.g. US, FR. Matched against the ad’s ad_reached_countries.
mediaType'image' | 'video' | 'unknown'noCoarse media type derived from the crawled creative.
languagestring (BCP-47 short tag)noAI-detected primary language of the ad copy (e.g. en, fr).
brandId`brn_${string}`noRestrict to a single Brand. Use list_brands (coming soon) to discover IDs.
format'image_static' | 'image_carousel' | 'video' | 'unknown'noAI-derived format from the ad’s Analysis.
themestringnoSingle AI-derived theme (matched against the ad’s analysisThemes).
status'ACTIVE' | 'INACTIVE'noProvider-reported delivery status at last crawl. INACTIVE ads stay in the corpus for historical search.
activeFromnumber (epoch ms)noEarliest provider start time. Inclusive. Filters on the ad’s delivery window, not corpus ingestion date.
activeTonumber (epoch ms)noLatest provider start time. Inclusive. Pair with activeFrom for a window query.
limitnumbernoPage size. Default 50, clamped to 200.
offsetnumbernoPagination offset. Ignored when q is provided.
sort'created_desc' | 'created_asc' | 'start_time_desc' | 'start_time_asc'noOrder of results. created_* sorts by corpus ingestion time; start_time_* by provider delivery start. Ignored when q is provided. Default created_desc.

Output

{ data: LibraryAd[], pagination: { limit, offset, total } }.

Each LibraryAd carries the normalized ad (id lad_*, brandId, provider, mediaType, mediaUrls, pageName, body, startTime, stopTime, status, countries) plus its AI Analysis (visual description, hook, format, tone, themes, language). In semantic mode each result also has a score field with the cosine similarity to the query vector.

pagination.total:

  • In structured mode (no q): the total number of corpus rows matching the filters — agents can paginate confidently with offset.
  • In semantic mode (q present): the count of hits that survived hydration and post-filter, not a corpus-wide count. Capped at 100 (relevance past the first ~50 of a vector search is noise).

Examples

Semantic discovery

“Find ads about luxury skincare with a discount angle.”

search_ads({ q: 'luxury skincare discount offers' });

Returns the top semantic matches ranked by cosine similarity — works even when none of the ads use the exact words “luxury”, “skincare”, or “discount” in their copy.

“What’s the back-to-school messaging running in France right now?”

search_ads({
  q: 'back to school sale',
  country: 'FR',
  status: 'ACTIVE',
});

Semantic query narrowed by country and active status — composes filters with the vector search in a single call.

Filter by AI-derived facets

“Show me UGC-style testimonial ads.”

search_ads({ q: 'customer testimonial real person review' });

Or, if the Analysis pipeline has tagged a specific theme:

search_ads({ theme: 'testimonial', mediaType: 'video' });

“Pull every carousel ad we’ve seen for fashion brands.”

search_ads({ format: 'image_carousel', q: 'fashion apparel clothing' });

Browse by Brand

“What’s running for brn_abc123 right now?”

search_ads({
  brandId: 'brn_abc123',
  status: 'ACTIVE',
  sort: 'start_time_desc',
});

“Last 20 ads we ingested for brn_abc123.”

search_ads({ brandId: 'brn_abc123', limit: 20, sort: 'created_desc' });

Time-windowed analysis

“Show me TikTok ads that started running in October 2025.”

search_ads({
  provider: 'tiktok',
  activeFrom: Date.UTC(2025, 9, 1),
  activeTo: Date.UTC(2025, 9, 31, 23, 59, 59),
  sort: 'start_time_asc',
});

“Find Meta video ads about Black Friday that ran any time last year.”

search_ads({
  provider: 'meta',
  mediaType: 'video',
  q: 'black friday cyber monday deals',
  activeFrom: Date.UTC(2025, 0, 1),
  activeTo: Date.UTC(2025, 11, 31, 23, 59, 59),
});

Multilingual narrowing

“Find Spanish-language ads about online banking.”

search_ads({ q: 'online banking mobile app', language: 'es' });

The semantic query is embedded with a multilingual model (bge-m3), so it matches Spanish ad copy even when written in English.

Cross-provider competitive scan

“Across Meta and TikTok, find any ad mentioning a 30-day free trial.”

search_ads({ q: '30 day free trial no credit card', status: 'ACTIVE' });

Omit provider to scan every library at once. The unified Vectorize index makes cross-provider semantic search a single call.

Paginating a structured listing

“Walk me through all active Meta video ads in the US, 50 at a time.”

First call:

search_ads({
  provider: 'meta',
  mediaType: 'video',
  country: 'US',
  status: 'ACTIVE',
  limit: 50,
  offset: 0,
});

Next page (the agent reads pagination.total to know when to stop):

search_ads({
  provider: 'meta',
  mediaType: 'video',
  country: 'US',
  status: 'ACTIVE',
  limit: 50,
  offset: 50,
});

Semantic vs. structured

Pass q to ask conceptually (“ads about back-to-school discounts”). Omit q and pass structured filters to enumerate a known slice of the corpus. Structured filters compose with q in a single Vectorize query, so both modes accept the full filter set.

Pagination differs by mode

In structured mode, pagination.total is the corpus-wide match count, and offset walks the result set. In semantic mode, offset is ignored and pagination.total reflects post-hydration matches (capped at 100) — relevance past the first ~50 of a vector search is noise, so deep pagination is intentionally not supported.

Crawled, not live

search_ads reads the AdCrunch corpus — it never calls the providers at query time. If a brand you care about isn’t yet in the corpus, ask the agent to call request_crawl (coming soon) to enqueue it for the next crawl cycle. Fresh data appears on the next scheduled crawl.

Errors

  • 401 Unauthorized — the OAuth token has expired. Re-authorize from your AI client’s settings.