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
| Field | Type | Required | Description |
|---|---|---|---|
q | string | no | Free-text semantic query. When present, switches to vector search; sort and offset are ignored. |
provider | 'meta' | 'tiktok' | 'x' | 'snapchat' | no | Restrict to a single ad library. |
country | string (ISO-3166-1 alpha-2) | no | e.g. US, FR. Matched against the ad’s ad_reached_countries. |
mediaType | 'image' | 'video' | 'unknown' | no | Coarse media type derived from the crawled creative. |
language | string (BCP-47 short tag) | no | AI-detected primary language of the ad copy (e.g. en, fr). |
brandId | `brn_${string}` | no | Restrict to a single Brand. Use list_brands (coming soon) to discover IDs. |
format | 'image_static' | 'image_carousel' | 'video' | 'unknown' | no | AI-derived format from the ad’s Analysis. |
theme | string | no | Single AI-derived theme (matched against the ad’s analysisThemes). |
status | 'ACTIVE' | 'INACTIVE' | no | Provider-reported delivery status at last crawl. INACTIVE ads stay in the corpus for historical search. |
activeFrom | number (epoch ms) | no | Earliest provider start time. Inclusive. Filters on the ad’s delivery window, not corpus ingestion date. |
activeTo | number (epoch ms) | no | Latest provider start time. Inclusive. Pair with activeFrom for a window query. |
limit | number | no | Page size. Default 50, clamped to 200. |
offset | number | no | Pagination offset. Ignored when q is provided. |
sort | 'created_desc' | 'created_asc' | 'start_time_desc' | 'start_time_asc' | no | Order 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 withoffset. - In semantic mode (
qpresent): 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_abc123right 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.