GET /v1/projects/:projectId/metrics
Unified organic metrics across post, account, project, and layer scopes. One query shape, one response shape.
GET
/v1/projects/{projectId}/metricsPhase 1stable
- Auth
- Bearer
- Scope
- metrics:read
Organic metrics for anything you can identify - a single platform post, a social account, a project layer, a whole project, or your organization. scope controls what kind of thing you are asking about; id is the specific one; metrics, since, until, and granularity control what you get back.
Metrics always come back as (series, totals). series is the bucketed time series at your requested granularity; totals is the aggregate over the full window. You get both so your UI can render the chart and the big number without a second call.
For ranked creatives rather than raw time series, use GET /v1/projects/:projectId/top-performers.
Query
scopestringrequiredWhat you are asking about.One of:platform_post,social_account,project,project_layer,organizationidstringrequiredId of the scoped entity. platform_post takes a platform_post_id; social_account takes a social_account_id; project takes a project_id; project_layer takes a project_layer_id; organization takes the org id from /v1/whoami.metricsstring[]optionalWhich metrics to return. Omit for the default bundle for the scope.One of:views,reach,impressions,likes,comments,shares,saves,watch_time_ms,engagement_rate,followers,follower_delta,posts_publishedsincestring (ISO 8601, UTC Z)optionaldefault: 30 days agoWindow start. Inclusive.untilstring (ISO 8601, UTC Z)optionaldefault: nowWindow end. Exclusive.granularitystringoptionaldefault: daySeries bucket size.One of:hour,day,week
Example request
curl "https://api.layers.com/v1/projects/prj_254a4ce1-f4ca-42b1-9e36-17ca45ef3d39/metrics?scope=project&id=prj_254a4ce1-f4ca-42b1-9e36-17ca45ef3d39&metrics=views&metrics=engagement_rate&since=2026-04-01T00:00:00Z&until=2026-04-18T00:00:00Z&granularity=day" \
-H "Authorization: Bearer lp_..."const params = new URLSearchParams({
scope: "project",
id: "prj_254a4ce1-f4ca-42b1-9e36-17ca45ef3d39",
since: "2026-04-01T00:00:00Z",
until: "2026-04-18T00:00:00Z",
granularity: "day",
});
params.append("metrics", "views");
params.append("metrics", "engagement_rate");
const res = await fetch(
`https://api.layers.com/v1/projects/prj_254a4ce1-f4ca-42b1-9e36-17ca45ef3d39/metrics?${params}`,
{ headers: { Authorization: `Bearer ${apiKey}` } },
);
const { series, totals } = await res.json();import httpx
params = [
("scope", "project"),
("id", "prj_254a4ce1-f4ca-42b1-9e36-17ca45ef3d39"),
("since", "2026-04-01T00:00:00Z"),
("until", "2026-04-18T00:00:00Z"),
("granularity", "day"),
("metrics", "views"),
("metrics", "engagement_rate"),
]
r = httpx.get(
"https://api.layers.com/v1/projects/prj_254a4ce1-f4ca-42b1-9e36-17ca45ef3d39/metrics",
params=params,
headers={"Authorization": f"Bearer {api_key}"},
)
payload = r.json()Response
200OK
{
"scope": "project",
"id": "prj_254a4ce1-f4ca-42b1-9e36-17ca45ef3d39",
"window": {
"since": "2026-04-01T00:00:00Z",
"until": "2026-04-18T00:00:00Z",
"granularity": "day"
},
"series": [
{ "bucket": "2026-04-01", "views": 12840, "engagement_rate": 0.041 },
{ "bucket": "2026-04-02", "views": 17220, "engagement_rate": 0.052 },
{ "bucket": "2026-04-03", "views": 9880, "engagement_rate": 0.037 }
],
"totals": {
"views": 240911,
"engagement_rate": 0.047,
"post_count": 23
}
}422scope/id mismatch or unknown metric.
{ "error": { "code": "VALIDATION", "message": "Unknown metric: conversions. Use /v1/projects/:projectId/ads-metrics for paid metrics." } }404Scoped entity not found or not in your organization.
Defaults by scope
| Scope | Default metrics | Notes |
|---|---|---|
platform_post | views, likes, comments, shares, saves, engagement_rate | Single post; granularity=day or hour. |
social_account | followers, follower_delta, posts_published, views | Account-level roll-up. |
project_layer | views, engagement_rate, posts_published | Per-layer so you can compare Social Content vs Managed UGC side by side. |
project | Same as project_layer, summed across layers. | |
organization | views, posts_published across all projects. | Coarse - use for an overview pane. |
Notes
- Engagement rate is weighted-cumulative:
(sum likes + sum comments + sum shares + sum saves) / max(sum views, 1)over every post in the bucket (or in the totals window). This is the same math the Layers customer-facing dashboard renders. It is NOT the unweighted mean of per-postengagement_ratevalues — that aggregation overweights low-view posts and gave different numbers than the UI for the same query, so we picked weighted-cumulative as the single source of truth across both surfaces. Per-bucket and totals are computed the same way. watch_time_msis only populated for platforms that expose it (TikTok, Instagram Reels). Zero on static formats.- Paid metrics (spend, CPA, ROAS) live on
GET /v1/projects/:projectId/ads-metrics. Asking forspendhere returnsVALIDATION. - Windows longer than 90 days with
granularity=hourget clamped. Either narrow the window or step up today. scope=social_accountandscope=projectreads expand to sibling credentials automatically — when an account is reconnected, OAuth produces a newsocial_accountsrow and posts attach to whichever row was live at ingestion. The endpoint unions all rows for the same(platform, platform_user_id)within the project so totals match the dashboard.- Posts are bucketed by
published_at(the publish moment), not by metric snapshot time. Windows and buckets are UTC; the dashboard renders in the project timezone, so day-boundary numbers can differ by up to 24h for projects in non-UTC zones. If you need timezone-aligned daily numbers, requestgranularity=dayand group client-side, or window precisely to your local day boundaries.
See also
GET /v1/projects/:projectId/ads-metrics- paid metricsGET /v1/projects/:projectId/top-performers- ranked creatives- Publish to learn - the read-side feedback loop