Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/MatthewSabia1/AdRecon/llms.txt

Use this file to discover all available pages before exploring further.

AdRecon exposes three feed views that normalize raw ad data and scope it to user context. All views use security_invoker = true so RLS policies apply based on the calling user.

ads_feed_v2

Primary feed view. Normalizes apify_ads_raw into a clean ad schema with computed niche, network, status, and days running.

Purpose

  • Powers the “All Ads” scope in the dashboard
  • Applies classification functions (classify_niche, classify_network) to raw ad text
  • Computes days_running from start_date or updated_at
  • Computes creative_count from media arrays and carousel cards
  • Provides fallback values for missing fields

Columns

ad_archive_id
text
Meta Ad Library archive ID.
advertiser
text
Coalesced from page_name, snapshot.page_name, raw.page_name. Defaults to "Unknown Advertiser".
offer_name
text
Coalesced from snapshot.title, raw.snapshot.title, raw.ad_title, page_name. Defaults to "Ad {ad_archive_id}".
ad_copy
text
Coalesced from snapshot.body.text, snapshot.body, raw.snapshot.body.text, raw.ad_creative_body, raw.ad_copy, raw.description. Defaults to empty string.
niche
text
Computed via classify_niche(classification_text). Values: Health, Wealth, BizOpp, Relationship, Survival, Other.
network
text
Computed via classify_network(destination_url, classification_text). Values: ClickBank, Digistore24, BuyGoods, MaxWeb, Other.
days_running
int
Computed as floor((now() - coalesce(start_date, updated_at)) / 86400). Minimum 0.
status
text
"Active" if is_active is true or null, "Inactive" if false.
image_url
text
First available image from snapshot.images[0].original_image_url, snapshot.cards[0].original_image_url, media.imageUrls[0], video thumbnails, or fallback placeholder.
video_url
text
First available video from snapshot.videos[0].video_hd_url, snapshot.cards[0].video_hd_url, media.videoUrls[0], or null.
start_date
timestamptz
When the ad first went live.
destination_url
text
Coalesced from snapshot.link_url, snapshot.cards[0].link_url, source_url, ad_library_url. Defaults to Meta Ad Library URL.
cta_text
text
Call-to-action button text. Defaults to "Learn More".
headline
text
Coalesced from snapshot.title, raw.headline, page_name. Defaults to "Untitled Ad".
Coalesced from snapshot.link_description, snapshot.cards[0].link_description. Defaults to empty string.
ad_library_url
text
Direct link to the ad in Meta Ad Library.
updated_at
timestamptz
Last update timestamp from apify_ads_raw.
creative_count
int
Number of creative variants. Computed as max(1, imageUrls.length + videoUrls.length, cards.length).

Normalization Logic

The view uses cascading coalesce() to extract fields from multiple JSON paths:
coalesce(
  nullif(a.snapshot->>'title', ''),
  nullif(a.raw->'snapshot'->>'title', ''),
  nullif(a.raw->>'ad_title', ''),
  'Ad ' || a.ad_archive_id
) as offer_name
This pattern:
  1. Tries snapshot.title
  2. Falls back to raw.snapshot.title
  3. Falls back to raw.ad_title
  4. Defaults to "Ad {id}" if all are null/empty

Classification Functions

classify_niche(classification_text)

Regex-based niche classification:
PatternNiche
weight|metabolism|supplement|health|wellness|fat burn|prostate|vision|joint|energy|dietHealth
wealth|crypto|bitcoin|income|money|finance|trading|invest|stock|retirement|cashflowWealth
biz opp|business opportunity|affiliate|make money online|lead gen|agency|ecommerce|dropshipBizOpp
relationship|dating|marriage|text him|love|obsession|attract|breakupRelationship
survival|prepper|off grid|grid down|emergency|collapse|self defenseSurvival
none matchOther

classify_network(destination_url, classification_text)

Regex-based affiliate network detection:
PatternNetwork
clickbank|hop.clickbank.net|clkbankClickBank
digistore24|digistoreDigistore24
buygoodsBuyGoods
maxwebMaxWeb
none matchOther
Checks destination_url first, then classification_text.
classification_text is a lowercase concatenation of all text fields from page_name, snapshot, raw, used as input to both classifiers.

user_saved_ads_feed_v1

Joins user_saved_ads with ads_feed_v2 to show only ads the current user has saved.

Purpose

Powers the “Saved” scope in the dashboard.

Columns

user_id
uuid
The user who saved the ad. Scoped by RLS to auth.uid().
*
all columns from ads_feed_v2
All columns from ads_feed_v2 (ad_archive_id, advertiser, offer_name, ad_copy, niche, network, days_running, status, image_url, video_url, start_date, destination_url, cta_text, headline, link_description, ad_library_url, updated_at, creative_count).

SQL Definition

create or replace view public.user_saved_ads_feed_v1
with (security_invoker = true)
as
select
  s.user_id,
  a.*
from public.user_saved_ads s
join public.ads_feed_v2 a on a.ad_archive_id = s.ad_archive_id;

Filtering Behavior

  • Only shows ads where user_saved_ads.user_id = auth.uid() (enforced by RLS on user_saved_ads)
  • Inner join ensures only ads still present in apify_ads_raw are returned
  • If an ad is deleted from apify_ads_raw, the cascade delete removes the user_saved_ads row, so the ad disappears from this view

user_project_ads_feed_v1

Joins user_saved_ad_projects with ads_feed_v2 to show ads in a specific project.

Purpose

Powers the “Project” scope in the dashboard when a project is selected.

Columns

user_id
uuid
The user who owns the project. Scoped by RLS to auth.uid().
project_id
uuid
The project ID. Dashboard filters on this column.
*
all columns from ads_feed_v2
All columns from ads_feed_v2.

SQL Definition

create or replace view public.user_project_ads_feed_v1
with (security_invoker = true)
as
select
  p.user_id,
  p.project_id,
  a.*
from public.user_saved_ad_projects p
join public.ads_feed_v2 a on a.ad_archive_id = p.ad_archive_id;

Filtering Behavior

  • Only shows ads where user_saved_ad_projects.user_id = auth.uid() (enforced by RLS)
  • Dashboard adds project_id = <selected_project_id> filter
  • Inner join ensures only ads still in apify_ads_raw are shown
  • If a project is deleted, cascade delete removes all user_saved_ad_projects rows, so ads disappear from this view for that project

Feed Query Contract

The frontend (src/lib/ads.ts) queries these views via fetchAdsPage(params) where params.scope determines the source:
ScopeViewAdditional Filter
allads_feed_v2None
saveduser_saved_ads_feed_v1None (RLS scopes to current user)
projectuser_project_ads_feed_v1project_id = params.projectId

Common Filters Applied

  • Search: OR-matches offer_name, advertiser, ad_copy via ilike
  • Niche: Exact match on niche column
  • Network: Exact match on network column
  • Status: Exact match on status column
  • Performance Bucket: Range filter on days_running (New: ≤14, Promising: ≥7, Profitable: ≥30, Proven: ≥60)

Fallback Behavior

classify_niche and classify_network functions process raw::text via regex, which is expensive. Server-side filters on these columns can cause statement timeouts (HTTP 500).
When timeouts occur, the data layer:
  1. Falls back to updatedAt desc sort if daysRunning sort times out
  2. Fetches IDs without expensive filters, then applies niche/network matching client-side in JavaScript
  3. Uses ID-first retrieval with probe batches to scan and match rows incrementally
See src/lib/ads.ts:1556 (fetchAdsPage) for full fallback chain.

Performance Notes

  • Indexes: The views themselves are not indexed. Queries rely on indexes on apify_ads_raw.ad_archive_id, user_saved_ads(user_id, ad_archive_id), and user_saved_ad_projects composite keys.
  • Statement timeout: PostgREST default is 60s. Broad queries with niche/network filters can exceed this. The app uses client-side filtering fallback to handle timeouts gracefully.
  • Pagination: Uses PostgREST range() for offset-based pagination. Client-side dedup logic in src/lib/ads.ts removes duplicate ads by content hash.
creative_count was added in migration 20260226_add_creative_count_to_feed.sql to support performance bucket classification. Downstream views (user_saved_ads_feed_v1, user_project_ads_feed_v1) inherit it via a.*.