Layers
Partner APIAPI referenceMetrics

GET /v1/projects/:projectId/ads-content

Scored creatives - generated content and UGC with organicScore, scoringPool, and eligibility flags for ad promotion.

View as Markdown
GET/v1/projects/{projectId}/ads-content
Phase 1stable
Auth
Bearer
Scope
metrics:read

Returns scored creatives for a project: generated content, eligible UGC, and manually included items. Each item includes an organicScore (0–10), scoringPool (generated / ugc / manual), eligibility, and override state.

Use organicScore as the creative health signal, eligibility for the current promote/do-not-promote decision, and override to see whether a user has pinned the item in or out.

Path
  • projectId
    string (UUID)required
    Project to list within.
Query
  • scoringPool
    string[]optional
    Filter by pool. Repeat for multiple.
    One of: generated, ugc, manual
  • minScore
    numberoptionaldefault: 0
    Keep rows with organicScore >= minScore. 0 to 10.
  • override
    stringoptional
    Filter by override state.
    One of: include, exclude, none
  • eligible
    booleanoptional
    Keep only items whose current state clears the 4.0 threshold (or has override=include).
  • sort
    stringoptionaldefault: score_desc
    Sort order.
    One of: score_desc, score_asc, scored_at_desc
  • cursor
    stringoptional
    Opaque pagination cursor from nextCursor. Forged or non-UUID cursors are rejected and treated as no cursor (first page).
  • limit
    numberoptionaldefault: 50
    Page size, 1–200.

Example request

curl "https://api.layers.com/v1/projects/prj_254a4ce1-f4ca-42b1-9e36-17ca45ef3d39/ads-content?eligible=true&sort=score_desc&limit=25" \
  -H "Authorization: Bearer lp_..."
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

200OK
{
  "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": "api_key",
      "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 currently returned as an empty array. To discover where a creative is already running, call GET /v1/projects/:projectId/ads/ads?adsContentId=….

Nothing about organicScore pauses an already-running ad. Pause decisions should use live ad performance such as CPA, conversion rate, and fatigue. See Publish to learn for the full feedback loop.

Notes

  • Default score 7.0 with organicPerformance.best.isDefault: true means "not yet scored with real data" - fine for initial promotion, not a proof of performance.
  • scoringVersion bumps when the formula changes. Scores across versions are not apples-to-apples.
  • scoredAt lags real time. Do not treat the field as "now."
  • adsContentId and sourceId/platformPostId are returned as raw UUIDs without partner prefixes. Pass the raw UUID back to PATCH and to ads-list filters.
  • Paginate with cursor until nextCursor is null. Score-ranked lists can shift between requests - use eligible=true with a stable sort for the queue you iterate.

See also

On this page