GET /v1/projects/:projectId/ads-content
Scored creatives — generated content and UGC with organicScore, scoringPool, and eligibility flags for ad promotion.
/v1/projects/{projectId}/ads-content- Auth
- Bearer
- Scope
- metrics:read
The central scoring table for ad creatives. Every generated container and every eligible UGC post has a row here with an organicScore (0–10), a scoringPool (generated / ugc / manual), and enough context for an agent to decide what to promote, refresh, or exclude.
This is where a partner agent reads the signal Layers' own optimizer reads. Use organicScore as the "how healthy is this creative?" gauge; use eligibility for the yes/no of whether the current threshold would let it run as an ad; use override to see if a human or earlier agent decision has already pinned it.
projectIdstring (UUID)requiredProject to list within.
scoringPoolstring[]optionalFilter by pool. Repeat for multiple.One of:generated,ugc,manualminScorenumberoptionaldefault: 0Keep rows with organicScore >= minScore. 0 to 10.overridestringoptionalFilter by override state.One of:include,exclude,noneeligiblebooleanoptionalKeep only items whose current state clears the 4.0 threshold (or has override=include).sortstringoptionaldefault: score_descSort order.One of:score_desc,score_asc,scored_at_desccursorstringoptionalOpaque pagination cursor from nextCursor. Forged or non-UUID cursors are rejected and treated as no cursor (first page).limitnumberoptionaldefault: 50Page size, 1–200.
Example request
curl "https://api.layers.com/v1/projects/prj_01HX9Y7K8M2P4RSTUV56789AB/ads-content?eligible=true&sort=score_desc&limit=25" \
-H "Authorization: Bearer lp_live_01HX9Y6K7EJ4T2_4QZpN..."const res = await fetch(
`https://api.layers.com/v1/projects/${projectId}/ads-content?eligible=true&sort=score_desc&limit=25`,
{ headers: { Authorization: `Bearer ${apiKey}` } },
);
const { items, nextCursor } = await res.json();
// Promote the top 3 that aren't already promoted.
const candidates = items
.filter((i) => i.activePromotions?.length === 0)
.slice(0, 3);import httpx
r = httpx.get(
f"https://api.layers.com/v1/projects/{project_id}/ads-content",
params={"eligible": True, "sort": "score_desc", "limit": 25},
headers={"Authorization": f"Bearer {api_key}"},
)
items = r.json()["items"]Response
{
"items": [
{
"adsContentId": "01HXC9A2B3C4D5E6F7G8H9J0K1",
"sourceType": "content_container",
"sourceId": "01HXC8A2B3C4D5E6F7G8H9J0K1",
"platformPostId": null,
"scoringPool": "generated",
"organicScore": 8.4,
"organicPerformance": {
"best": { "isDefault": false, "views": 51200, "engagement_rate": 0.062 },
"adBoost": 1.9
},
"adPerformance": { "spend": 63.50, "conversions": 22, "cpa": 2.89, "roas": 4.1 },
"scoredAt": "2026-04-18T12:00:00Z",
"scoringVersion": 3,
"eligibility": { "isEligible": true, "reason": "organic_score 8.4 meets threshold" },
"override": null,
"overrideNote": null,
"overrideSetBy": null,
"overrideSetAt": null,
"activePromotions": []
},
{
"adsContentId": "01HXD2A2B3C4D5E6F7G8H9J0K1",
"sourceType": "platform_post",
"sourceId": null,
"platformPostId": "01HXD1A2B3C4D5E6F7G8H9J0K1",
"scoringPool": "ugc",
"organicScore": 6.1,
"organicPerformance": { "percentiles": { "quality": 0.71, "reach": 0.68 } },
"adPerformance": null,
"scoredAt": "2026-04-18T12:00:00Z",
"scoringVersion": 3,
"eligibility": { "isEligible": true, "reason": "included by override" },
"override": "include",
"overrideNote": null,
"overrideSetBy": "lp_live_HHMRNJ2AHYJ6WZJ4 (api_key_id)",
"overrideSetAt": "2026-04-15T18:00:00Z",
"activePromotions": []
}
],
"nextCursor": null
}Reading the signals
organicScore is on a 0–10 scale. Above 4.0 is eligible to run as an ad. The math differs by pool:
generated— default 7.0 at creation, decays linearly toward 2.0 over 30 days. Good ad performance tacks up to +3.0 back on, so a winning ad can sit near 10 indefinitely.ugc— starts at 0 until real platform metrics arrive. Scored on quality and reach percentiles within the pool, with a 90-day freshness decay.manual— operator-set. Treated as authoritative until someone changes it.
eligibility.isEligible is the simple yes/no: does the current state clear the 4.0 bar, accounting for any override? override: "include" bypasses the threshold; override: "exclude" forces ineligible regardless of score. null means "follow the score."
activePromotions is reserved for a future enrichment that will list every layer_meta_ads / layer_tiktok_ads junction holding this adsContentId. It is always [] in Phase 1 — run GET /v1/projects/:projectId/ads/ads?adsContentId=… to discover where a creative is already running.
How Layers itself uses these signals
Layers' ad refresh loop reads the same rows: select new eligible creatives, deactivate underperforming ads, respect overrides. Nothing about organicScore pauses an already-running ad — pause decisions are made from live ad performance (CPA, conversion rate, fatigue), not from decay. Your agent should do the same. See Publish to learn for the full feedback loop, including when to promote more of a winner versus refresh a fatigued creative.
Notes
- Default score
7.0withorganicPerformance.best.isDefault: truemeans "not yet scored with real data" — fine for initial promotion, not a proof of performance. scoringVersionbumps when the formula changes. Scores across versions are not apples-to-apples.scoredAtlags real time. Scoring runs on a six-hour cron. Do not treat the field as "now."adsContentIdandsourceId/platformPostIdare returned as raw UUIDs without partner prefixes. Pass the raw UUID back to PATCH and to ads-list filters.- Paginate with
cursoruntilnextCursorisnull. Score-ranked lists can shift between requests — useeligible=truewith a stable sort for the queue you iterate.
See also
PATCH /v1/projects/:projectId/ads-content/:id— pin a creative with include or excludeGET /v1/projects/:projectId/top-performers— cross-source ranking- Publish to learn — the feedback loop
GET /v1/ads-metrics— paid performance by scope
GET /v1/projects/:projectId/top-performers
Cross-source ranking of creatives by views, engagement rate, conversions, ROAS, or watch time — one row per creative, pre-ranked.
PATCH /v1/projects/:projectId/ads-content/:id
Force-include or force-exclude a creative from ad rotation, overriding the organic score gate.