GET /v1/projects/:projectId/ads-metrics
Paid performance — spend, CPA, ROAS, CPC, CPM — across Meta, TikTok, and Apple at campaign, adset, ad, or account scope.
/v1/projects/{projectId}/ads-metrics- Auth
- Bearer
- Scope
- metrics:read
Paid performance metrics across Meta, TikTok, and Apple Search Ads. Same shape as /v1/metrics — one query, (series, totals) back — but scoped to ad-manager entities (campaigns, ad sets, ads, ad accounts) and carrying cost-and-conversion metrics instead of organic reach.
Use this for performance analysis, not creative selection. For "what should I promote?" go to ads-content or top-performers. This endpoint answers "what did my ads do?"
scopestringrequiredAd entity you are asking about.One of:ad,adset,campaign,ad_account,projectidstringrequiredId of the scoped entity. ad takes adId; adset takes adsetId; campaign takes campaignId; ad_account takes adAccountId; project takes projectId.metricsstring[]optionalMetrics to return. Omit for the default bundle for the scope.One of:spend,impressions,clicks,conversions,cpa,cpc,cpm,ctr,roas,reach,frequencyplatformsstring[]optionalRestrict to one or more platforms. Only meaningful at ad_account and project scope.One of:meta_ads,tiktok_ads,apple_adssincestring (ISO-8601)optionaldefault: 30 days agoWindow start. Inclusive.untilstring (ISO-8601)optionaldefault: nowWindow end. Exclusive.granularitystringoptionaldefault: daySeries bucket size.One of:day,week
Example request
curl "https://api.layers.com/v1/projects/prj_01HX9Y7K8M2P4RSTUV56789AB/ads-metrics?scope=campaign&id=cmp_01HXG9&metrics=spend&metrics=cpa&metrics=roas&since=2026-04-01&until=2026-04-18" \
-H "Authorization: Bearer lp_live_01HX9Y6K7EJ4T2_4QZpN..."const params = new URLSearchParams({
scope: "campaign",
id: "cmp_01HXG9",
since: "2026-04-01",
until: "2026-04-18",
granularity: "day",
});
for (const m of ["spend", "cpa", "roas"]) params.append("metrics", m);
const res = await fetch(
`https://api.layers.com/v1/projects/${projectId}/ads-metrics?${params}`,
{ headers: { Authorization: `Bearer ${apiKey}` } },
);
const { series, totals } = await res.json();import httpx
params = [
("scope", "campaign"),
("id", "cmp_01HXG9"),
("since", "2026-04-01"),
("until", "2026-04-18"),
("granularity", "day"),
("metrics", "spend"),
("metrics", "cpa"),
("metrics", "roas"),
]
r = httpx.get(
f"https://api.layers.com/v1/projects/{project_id}/ads-metrics",
params=params,
headers={"Authorization": f"Bearer {api_key}"},
)
payload = r.json()Response
{
"scope": "campaign",
"id": "cmp_01HXG9...",
"platform": "meta_ads",
"window": {
"since": "2026-04-01T00:00:00Z",
"until": "2026-04-18T00:00:00Z",
"granularity": "day"
},
"series": [
{ "bucket": "2026-04-01", "spend": 142.50, "conversions": 38, "cpa": 3.75, "roas": 3.4 },
{ "bucket": "2026-04-02", "spend": 167.22, "conversions": 45, "cpa": 3.72, "roas": 3.6 },
{ "bucket": "2026-04-03", "spend": 98.40, "conversions": 24, "cpa": 4.10, "roas": 3.1 }
],
"totals": {
"spend": 2819.66,
"conversions": 732,
"cpa": 3.85,
"roas": 3.42,
"impressions": 842100,
"clicks": 21408,
"ctr": 0.0254
}
}{ "error": { "code": "VALIDATION", "message": "Metric 'watch_time_ms' is organic-only. Use /v1/metrics." } }Defaults by scope
| Scope | Default metrics | Notes |
|---|---|---|
ad | spend, impressions, clicks, conversions, cpa, roas | Single ad across its lifetime on the platform. |
adset | Same as ad, summed across the adset's ads. | |
campaign | Same as adset, summed across the campaign. | |
ad_account | spend, conversions, cpa, roas across all campaigns in the account. | platforms parameter implicit per account. |
project | spend, conversions, cpa, roas across every ad account on the project. | Use platforms to slice. |
Metric definitions
These are Layers' normalized definitions. Platforms report each metric differently — we compute each from normalized primitives (spend, impressions, clicks, conversions) rather than passing through platform values, so cross-platform comparisons mean what you'd expect.
cpa=spend / max(conversions, 1)cpc=spend / max(clicks, 1)cpm=spend / max(impressions, 1) * 1000ctr=clicks / max(impressions, 1)roas=conversionValue / max(spend, 0.01)— conversion value comes from the SDK event payload; ads without value reporting (app installs, leads) returnnullfrequency= Meta-only primitive;nullon TikTok and Apple
Notes
- Bucket edges are in UTC. A "day" bucket for
2026-04-01includes events from2026-04-01T00:00:00Zto2026-04-02T00:00:00Z. - Ad platforms backfill. Yesterday's numbers can shift for up to 72 hours, particularly on iOS where SKAN postbacks land late. Cache with a short TTL.
conversionsis the Layers-attributed count, sourced from the SDK → CAPI pipeline plus platform conversion APIs. It will not match the platform's own dashboard exactly — Layers dedupes across sources and applies its own attribution window.- Organic metrics (views, engagement_rate) are not available here. Use
/v1/metrics. granularity=houris not supported — paid reporting APIs on Meta and TikTok do not expose hourly reliably. Day is the smallest bucket.
See also
GET /v1/metrics— organic metrics with the same shapeGET /v1/projects/:projectId/top-performers— ranked creatives across organic and paidGET /v1/projects/:projectId/ads/campaigns— campaign list with 7-day metric snapshots