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.
/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 — one row per creative, 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 having to page through /v1/metrics and join 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.
Example request
curl "https://api.layers.com/v1/projects/prj_01HX9Y7K8M2P4RSTUV56789AB/top-performers?metric=roas&window=30d&limit=10" \
-H "Authorization: Bearer lp_live_01HX9Y6K7EJ4T2_4QZpN..."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 row of items) {
console.log(row.rank, row.metricValue, row.sourceType, row.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_01HXC8...",
"platformPostId": null,
"adsContentId": "adc_01HXC9...",
"title": "Before-and-after latte tutorial",
"thumbnailUrl": "https://media.layers.com/prj_.../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_01HXD2...",
"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 row per creative
A single creative can run on multiple platforms and as multiple ads. This endpoint collapses that down: one row per ads_content row, 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