Layers

Ad Eligibility & Creative Selection

How Layers picks which creatives go into an adset. The 3-pool selection model and per-adset budget caps.

View as Markdown

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:

  1. Filter to eligible ads_content within the project (organic_score >= 4.0 or override = 'include'; never override = 'exclude' or safetyFailed = true).
  2. Bucket candidates into three pools: generated, ugc, manual.
  3. Rank within each pool by organic_score (weighted by organicScoreWeight).
  4. Pick top N per pool, where N defaults to generatedSlots = 3, ugcSlots = 3, manualSlots = 3, capped by maxCreativesPerAdset (default 10) and the platform's hard limit (Meta caps adsets at 50 ads).
  5. 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:

  1. Disable underperformers — applies optimizer-style scoring with guardrails (min 3 days age, min $5 spent, never TOP_PERFORMER/HEALTHY, never empty an adset/adgroup).
  2. Select top creatives — pulls the next-best eligible creatives from each pool (generated, ugc, manual).
  3. 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

On this page