Docs
APIAPI referenceMetrics

GET /v1/projects/:projectId/top-performers

Cross-source ranking of creatives by views, engagement rate, conversions, ROAS, or watch time.

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

Returns the top-N creatives in a project, ranked by a single metric across an explicit window. Generated containers, UGC posts, and manual uploads are pooled together, with both the organic signal (views, engagement) and the paid signal (conversions, ROAS) attached. This is the shortcut for "what should I promote right now?" without paging through /v1/metrics and joining across sources yourself.

The list is already sorted by the metric you asked for. No client-side sort needed. No per-creative metric roll-ups needed. Pre-ranking is why this endpoint exists as a separate call rather than a flag on unified metrics.

Path
  • projectId
    string (UUID)required
    Project to rank within.
Query
  • metric
    stringrequired
    Ranking dimension.
    One of: views, engagement_rate, conversions, roas, watch_time_ms
  • window
    stringoptionaldefault: 30d
    Window to evaluate over.
    One of: 7d, 30d, 90d
  • sourceType
    string[]optional
    Restrict to one or more sources.
    One of: content_container, platform_post, manual
  • platform
    string[]optional
    Restrict to posts on these platforms. Ignores paid-only sources.
    One of: instagram, tiktok, youtube, meta_ads, tiktok_ads, apple_ads
  • limit
    numberoptionaldefault: 25
    Number of rows to return, 1–100.
  • includeIneligible
    booleanoptionaldefault: false
    Opt out of the eligibility gate. By default a post is dropped when Layers could not promote its creative right now, or when it has no creative record at all (nothing to promote). Set to `true` to surface every post regardless.

Eligibility filter

By default, results are filtered to eligible creatives, using the same predicate as /v1/projects/:projectId/ads-content and the same gate the platform applies before promoting a creative. A post is dropped when its creative is:

  • excluded by override (override = 'exclude'),
  • safety-failed (eligibility.reason: "safety_failed"),
  • flagged by a moderator (eligibility.reason: "flagged"),
  • unable to launch on its TikTok Spark authorization (eligibility.reason: "spark_authorization_not_live" or "spark_window_unknown"), or
  • missing the media behind it (eligibility.reason: "source_unavailable"),

or when it has no creative record in this project at all: there is nothing to promote, so it is not ranked as eligible. The deprecated creative score is not consulted; the ranking itself is on observed metrics (views, engagement rate) and paid results (conversions, ROAS), which are unaffected.

This applies to both the organic and paid paths:

  • Organic (metric=views, engagement_rate): platform posts whose creative is refused by any of the rules above are dropped before ranking. The ranking pages past them, so a refused post never costs you a slot — the list still returns your top limit promotable posts.
  • Paid (metric=conversions, roas): mapped creatives refused by any of the rules above are dropped. Unmapped paid rows (no adsContentId) pass through — there is no creative to gate on. Paid rows come only from campaigns imported into the project.

Opting out

To see the dropped posts too, including posts with no creative record, set ?includeIneligible=true to bypass the gate. The metric values are the same observed numbers; nothing is recomputed.

Example request

curl "https://api.layers.com/v1/projects/prj_254a4ce1-f4ca-42b1-9e36-17ca45ef3d39/top-performers?metric=roas&window=30d&limit=10" \
  -H "Authorization: Bearer lp_..."
const res = await fetch(
  `https://api.layers.com/v1/projects/${projectId}/top-performers?metric=roas&window=30d&limit=10`,
  { headers: { Authorization: `Bearer ${apiKey}` } },
);
const { items } = await res.json();

// Already sorted - just walk it.
for (const item of items) {
  console.log(item.rank, item.metricValue, item.sourceType, item.sourceId);
}
import httpx

r = httpx.get(
    f"https://api.layers.com/v1/projects/{project_id}/top-performers",
    params={"metric": "roas", "window": "30d", "limit": 10},
    headers={"Authorization": f"Bearer {api_key}"},
)
items = r.json()["items"]

Response

200OK - already sorted by the requested metric, descending.
{
  "metric": "roas",
  "window": "30d",
  "items": [
    {
      "rank": 1,
      "sourceType": "content_container",
      "sourceId": "cnt_7d18b9a1...",
      "platformPostId": null,
      "adsContentId": "adc_6f5d4c3b...",
      "title": "Before-and-after latte tutorial",
      "thumbnailUrl": "https://media.layers.com/prj_254a4ce1.../thumb_01HXC8.jpg",
      "metricValue": 5.2,
      "organic": {
        "views": 148200,
        "engagementRate": 0.061,
        "platforms": ["instagram", "tiktok"]
      },
      "paid": {
        "spend": 412.00,
        "conversions": 88,
        "roas": 5.2,
        "cpa": 4.68
      }
    },
    {
      "rank": 2,
      "sourceType": "platform_post",
      "sourceId": null,
      "platformPostId": "pp_a4f1c9d2-3e5b-4a76-8c19-2d0f7b6e5a34",
      "adsContentId": "adc_6f5d4c3b...",
      "title": "Creator pour-over UGC",
      "thumbnailUrl": "https://media.meetsift.com/.../cover.jpg",
      "metricValue": 4.7,
      "organic": {
        "views": 88100,
        "engagementRate": 0.079,
        "platforms": ["tiktok"]
      },
      "paid": {
        "spend": 210.00,
        "conversions": 41,
        "roas": 4.7,
        "cpa": 5.12
      }
    }
  ]
}
422Validation failed - metric missing, unknown enum, or `metric=roas` paired with an organic-only source mix.
{ "error": { "code": "VALIDATION", "message": "metric=roas requires paid data. Filter sourceType to content_container or manual, or pick an organic metric." } }

One item per creative

A single creative can run on multiple platforms and as multiple ads. This endpoint collapses that down to one item per creative, with organic and paid metrics summed across surfaces in the window. Use /v1/metrics?scope=platform_post if you need per-platform breakouts.

The window parameter is explicit - there is no "all time." Windows are evaluated at query time against platform and ad sync data, so results can shift between calls if a sync completes mid-flight.

Metric availability by source

Sourceviewsengagement_ratewatch_time_msconversionsroas
content_containerYesYesYesOnly if promotedOnly if promoted
platform_postYesYesYes (TikTok/Reels)Only if promotedOnly if promoted
manualNoNoNoOnly if promotedOnly if promoted

Ranking by a paid metric implicitly filters out creatives that were never promoted, and only counts spend from campaigns imported into the project. Ranking by an organic metric includes everything with platform metrics.

Notes

  • thumbnailUrl is permanent for UGC and generated content. Do not cache ad-platform thumbnail URLs from other endpoints - those expire.
  • metricValue is the same number as the field inside organic or paid - surfaced at the top level so your UI can render "5.2x" without guessing which subtree it came from.
  • Ties break on adsContentId descending, which stabilizes the order between requests.
  • Windows 7d/30d/90d end at query time. There is no offset or custom range; use unified metrics for that.

See also

On this page