Ad Eligibility & Creative Selection
How Layers picks which creatives go into an adset. The 3-pool selection model and per-adset budget caps.
When the optimizer launches a new ad or refreshes an adset, it pulls from your
eligible creative pool. Eligibility = organic_score >= 4.0 OR
override = 'include', AND the creative is not in override = 'exclude',
AND the creative does not have safetyFailed = true.
Selection algorithm
Creative selection runs each time the content-refresh workflow (or an ad-manager create/refresh flow) needs to (re)populate one or more adsets:
- Filter to eligible
ads_contentwithin the project (organic_score >= 4.0oroverride = 'include'; neveroverride = 'exclude'orsafetyFailed = true). - Bucket candidates into three pools:
generated,ugc,manual. - Rank within each pool by
organic_score(weighted byorganicScoreWeight). - Pick top N per pool, where N defaults to
generatedSlots = 3,ugcSlots = 3,manualSlots = 3, capped bymaxCreativesPerAdset(default 10) and the platform's hard limit (Meta caps adsets at 50 ads). - Skip adsets already at capacity — adsets already holding 50 ads on Meta are excluded from the refresh.
Per-pool slot counts are passed as workflow input — tune them per project by overriding the content-refresh input mapping.
Budget change guardrails
The optimizer can adjust adset budgets up or down, but:
- No single change exceeds 50% of current budget.
- No single change exceeds $100 absolute.
- Each entity is on a 48h cooldown between budget changes.
These caps prevent runaway scaling on a single optimizer cycle.
Refresh cadence
The content-refresh workflow runs every 15 minutes per ad layer:
- Disable underperformers — applies optimizer-style scoring with guardrails (min 3 days age, min $5 spent, never TOP_PERFORMER/HEALTHY, never empty an adset/adgroup).
- Select top creatives — pulls the next-best eligible creatives from
each pool (
generated,ugc,manual). - Launch the selected creatives into the freed slots.
A creative whose organic_score drops below threshold while it's already
running keeps running — score is only checked at selection time.
Why your "best" creative isn't running
Common reasons:
- It's
override = 'exclude'somewhere. - Its score is below 4.0 (check the per-creative score history).
- The adset already has its per-pool slots filled with higher scorers.
- Meta adset is at its 50-ad capacity — refresh skips it.
- It has
safetyFailed = true— regenerate it.
Manual overrides
Use project_ads_content.override = 'include' to keep a creative permanently
eligible regardless of score, or override = 'exclude' to force it
ineligible. Overrides are per-project, so the same ads_content row can be
eligible in one project and excluded in another.
Next
- Optimizer — what happens after selection.
- Refresh cycles — the content refresh loop.