# GET /v1/projects/:projectId/ads-metrics (/docs/api/reference/ads/ads-metrics)



<Endpoint method="GET" path="/v1/projects/{projectId}/ads-metrics" auth="Bearer" scope="metrics:read" phase="1" />

Paid performance metrics across Meta, TikTok, and Apple Search Ads. Same shape as [`/v1/metrics`](/docs/api/reference/metrics/unified-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`](/docs/api/reference/metrics/ads-content) or [`top-performers`](/docs/api/reference/metrics/top-performers). This endpoint answers "what did my ads do?"

<Parameters
  title="Query"
  rows="[
  { name: 'scope', type: 'string', required: true, description: 'Ad entity you are asking about.', enum: ['ad', 'adset', 'campaign', 'ad_account', 'project'] },
  { name: 'id', type: 'string', required: true, description: 'Id of the scoped entity. ad takes adId; adset takes adsetId; campaign takes campaignId; ad_account takes adAccountId; project takes projectId.' },
  { name: 'metrics', type: 'string[]', description: 'Metrics to return. Omit for the default bundle for the scope.', enum: ['spend', 'impressions', 'clicks', 'conversions', 'cpa', 'cpc', 'cpm', 'ctr', 'roas', 'reach', 'frequency'] },
  { name: 'platforms', type: 'string[]', description: 'Restrict to one or more platforms. Only meaningful at ad_account and project scope.', enum: ['meta_ads', 'tiktok_ads', 'apple_ads'] },
  { name: 'since', type: 'string (ISO-8601)', description: 'Window start. Inclusive.', default: '30 days ago' },
  { name: 'until', type: 'string (ISO-8601)', description: 'Window end. Exclusive.', default: 'now' },
  { name: 'granularity', type: 'string', description: 'Series bucket size.', enum: ['day', 'week'], default: 'day' },
]"
/>

## Example request [#example-request]

<Tabs items="['curl', 'TypeScript', 'Python']">
  <Tab value="curl">
    ```bash
    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..."
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    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();
    ```
  </Tab>

  <Tab value="Python">
    ```python
    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()
    ```
  </Tab>
</Tabs>

## Response [#response]

<Response status="200" description="OK">
  ```json
  {
    "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
    }
  }
  ```
</Response>

<Response status="400" description="Metric unavailable for the scope or platform asked.">
  ```json
  { "error": { "code": "VALIDATION", "message": "Metric 'watch_time_ms' is organic-only. Use /v1/metrics." } }
  ```
</Response>

<Response status="404" description="Entity not found or not in your organization." />

## Defaults by scope [#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 [#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) * 1000`
* `ctr` = `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) return `null`
* `frequency` = Meta-only primitive; `null` on TikTok and Apple

## Notes [#notes]

* Bucket edges are in UTC. A "day" bucket for `2026-04-01` includes events from `2026-04-01T00:00:00Z` to `2026-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.
* `conversions` is 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`](/docs/api/reference/metrics/unified-metrics).
* `granularity=hour` is not supported — paid reporting APIs on Meta and TikTok do not expose hourly reliably. Day is the smallest bucket.

## See also [#see-also]

* [`GET /v1/metrics`](/docs/api/reference/metrics/unified-metrics) — organic metrics with the same shape
* [`GET /v1/projects/:projectId/top-performers`](/docs/api/reference/metrics/top-performers) — ranked creatives across organic and paid
* [`GET /v1/projects/:projectId/ads/campaigns`](/docs/api/reference/ads/list-campaigns) — campaign list with 7-day metric snapshots
