GET /v1/projects/:projectId/top-performers
Cross-source ranking of creatives by views, engagement rate, conversions, ROAS, or watch time.
/v1/projects/{projectId}/top-performers- 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.
projectIdstring (UUID)requiredProject to rank within.
metricstringrequiredRanking dimension.One of:views,engagement_rate,conversions,roas,watch_time_mswindowstringoptionaldefault: 30dWindow to evaluate over.One of:7d,30d,90dsourceTypestring[]optionalRestrict to one or more sources.One of:content_container,platform_post,manualplatformstring[]optionalRestrict to posts on these platforms. Ignores paid-only sources.One of:instagram,tiktok,youtube,meta_ads,tiktok_ads,apple_adslimitnumberoptionaldefault: 25Number of rows to return, 1–100.includeIneligiblebooleanoptionaldefault: falseOpt out of the eligibility gate. By default the response only includes creatives the scoring pipeline already deemed eligible. Set to `true` to surface every creative regardless of `organic_score`.
Eligibility filter
By default, results are filtered to creatives the scoring pipeline already deemed eligible — organic_score >= 4.0 OR override = 'include', with override = 'exclude' always-ineligible. This matches the rule documented on /v1/projects/:projectId/ads-content and is the same gate the platform applies before promoting a creative.
This applies to both the organic and paid paths:
- Organic (
metric=views,engagement_rate,watch_time_ms): UGC platform posts withorganic_score < 4.0and noincludeoverride are dropped before ranking. - Paid (
metric=conversions,roas): mapped creatives withorganic_score < 4.0and noincludeoverride are dropped. Unmapped paid rows (noads_content_id) pass through — we have no scoring signal to gate on.
Opting out
For forensic exploration of low-score content, set ?includeIneligible=true to bypass the gate and surface every creative regardless of organic_score. The metric value reflects exactly what the scoring pipeline saw; 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
{
"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_01HXD1...",
"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
}
}
]
}{ "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
| Source | views | engagement_rate | watch_time_ms | conversions | roas |
|---|---|---|---|---|---|
content_container | Yes | Yes | Yes | Only if promoted | Only if promoted |
platform_post | Yes | Yes | Yes (TikTok/Reels) | Only if promoted | Only if promoted |
manual | No | No | No | Only if promoted | Only if promoted |
Ranking by a paid metric implicitly filters out creatives that were never promoted. Ranking by an organic metric includes everything with platform metrics.
Notes
thumbnailUrlis permanent for UGC and generated content. Do not cache ad-platform thumbnail URLs from other endpoints - those expire.metricValueis the same number as the field insideorganicorpaid- surfaced at the top level so your UI can render "5.2x" without guessing which subtree it came from.- Ties break on
adsContentIddescending, which stabilizes the order between requests. - Windows
7d/30d/90dend at query time. There is no offset or custom range; use unified metrics for that.
See also
GET /v1/projects/:projectId/ads-content- scored creatives with organic_score and eligibilityGET /v1/metrics- raw time series by scopeGET /v1/ads-metrics- paid metrics only