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



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

Lists individual ads under the project, across platforms. Phase 1 ships meta\_ads + tiktok\_ads only — Apple Search Ads has no equivalent layer junction (ASA bottoms out at the ad-group level), so it is intentionally excluded here. Each ad carries its parent adset and campaign and the `adsContentId` that lets you join back to [`ads-content`](/docs/api/reference/metrics/ads-content) when the ad was promoted from a Layers-scored creative.

<Callout type="warn">
  Read-only today. Creating, updating, pausing, or deleting ads is planned. Mutate in the platform's ad manager for now.
</Callout>

<Parameters
  title="Path"
  rows="[
  { name: 'projectId', type: 'string (UUID)', required: true, description: 'Project to list within.' },
]"
/>

<Parameters
  title="Query"
  rows="[
  { name: 'adsetId', type: 'string (UUID)', description: 'Restrict to ads in one ad set.' },
  { name: 'campaignId', type: 'string (UUID)', description: 'Restrict to ads in one campaign.' },
  { name: 'adAccountId', type: 'string (UUID)', description: 'Restrict to ads on one ad account.' },
  { name: 'platforms', type: 'string[]', description: 'Restrict to one or more platforms.', enum: ['meta_ads', 'tiktok_ads', 'apple_ads'] },
  { name: 'adsContentId', type: 'string (UUID)', description: 'Restrict to ads promoting this ads_content row. Useful for &#x22;where is this creative running?&#x22;' },
  { name: 'status', type: 'string[]', description: 'Filter by platform-native status.', enum: ['active', 'paused', 'archived', 'deleted', 'in_review', 'rejected'] },
  { name: 'cursor', type: 'string', description: 'Opaque pagination cursor. Forged or malformed cursors are rejected and treated as no cursor (first page).' },
  { name: 'limit', type: 'number', description: 'Page size, 1–200.', default: '100' },
]"
/>

## Example request [#example-request]

<Tabs items="['curl', 'TypeScript', 'Python']">
  <Tab value="curl">
    ```bash
    curl "https://api.layers.com/v1/projects/prj_01HX9Y7K8M2P4RSTUV56789AB/ads/ads?adsetId=adg_01HXG9&status=active" \
      -H "Authorization: Bearer lp_live_01HX9Y6K7EJ4T2_4QZpN..."
    ```
  </Tab>

  <Tab value="TypeScript">
    ```ts
    const res = await fetch(
      `https://api.layers.com/v1/projects/${projectId}/ads/ads?adsetId=${adsetId}&status=active`,
      { headers: { Authorization: `Bearer ${apiKey}` } },
    );
    const { items, nextCursor } = await res.json();

    // Find every platform a creative is live on.
    const byCreative = new Map<string, string[]>();
    for (const ad of items) {
      if (!ad.adsContentId) continue;
      const platforms = byCreative.get(ad.adsContentId) ?? [];
      platforms.push(ad.platform);
      byCreative.set(ad.adsContentId, platforms);
    }
    ```
  </Tab>

  <Tab value="Python">
    ```python
    import httpx

    r = httpx.get(
        f"https://api.layers.com/v1/projects/{project_id}/ads/ads",
        params={"adsetId": adset_id, "status": "active"},
        headers={"Authorization": f"Bearer {api_key}"},
    )
    items = r.json()["items"]
    ```
  </Tab>
</Tabs>

## Response [#response]

<Response status="200" description="OK">
  ```json
  {
    "items": [
      {
        "adId": "01HXGCA2B3C4D5E6F7G8H9J0K1",
        "adsetId": "01HXG9A2B3C4D5E6F7G8H9J0K1",
        "campaignId": "01HXG9B2B3C4D5E6F7G8H9J0K1",
        "adAccountId": "01HXF1A2B3C4D5E6F7G8H9J0K1",
        "platform": "meta_ads",
        "externalId": "23852014...",
        "name": "23852014...",
        "status": "active",
        "effectiveStatus": null,
        "adsContentId": null,
        "sourceType": null,
        "creative": null,
        "createdAt": "2026-03-29T15:04:00Z",
        "launchedAt": null,
        "metricsSnapshot7d": {}
      },
      {
        "adId": "01HXGDA2B3C4D5E6F7G8H9J0K1",
        "adsetId": "01HXGBA2B3C4D5E6F7G8H9J0K1",
        "campaignId": "01HXGBB2B3C4D5E6F7G8H9J0K1",
        "adAccountId": "01HXF2A2B3C4D5E6F7G8H9J0K1",
        "platform": "tiktok_ads",
        "externalId": "1785502...",
        "name": "1785502...",
        "status": "paused",
        "effectiveStatus": null,
        "adsContentId": "01HXD2A2B3C4D5E6F7G8H9J0K1",
        "sourceType": null,
        "creative": null,
        "createdAt": "2026-04-08T21:18:00Z",
        "launchedAt": null,
        "metricsSnapshot7d": {}
      }
    ],
    "nextCursor": null
  }
  ```
</Response>

## Joining ads to creatives [#joining-ads-to-creatives]

Every ad in this response has at most one `adsContentId`. That field is the bridge to [`ads-content`](/docs/api/reference/metrics/ads-content) (the scored creative), [`top-performers`](/docs/api/reference/metrics/top-performers) (cross-source ranking), and — for generated content — the source `content_container`. Use it to answer:

* **"Where is this creative running?"** Query `/ads?adsContentId=adc_01HXC9...`
* **"What score does this ad's creative have?"** Follow `adsContentId` to `/ads-content?adsContentId=...` or the item directly at the row level.
* **"Is this ad's creative manually overridden?"** Same path — the override lives on the `ads-content` row.

`adsContentId` is `null` only for ads that existed on the platform before Layers tracked them or for creatives outside the scoring pool (rare; mostly legacy imports). Do not rely on `null` being stable.

## Notes [#notes]

* `status` is the partner-normalized status mapped from the layer junction's record (one of `active`, `paused`, `archived`, `deleted`, `in_review`, `rejected`).
* `effectiveStatus`, `creative`, `sourceType`, `launchedAt`: reserved for future enrichment from platform-native ad rows. They are always `null` in Phase 1; partners that need creative metadata or the platform-derived effective status must call the platform's own ad manager.
* `metricsSnapshot7d` is reserved (always `{}` today). For paid performance, call [`GET /v1/projects/:projectId/ads-metrics`](/docs/api/reference/ads/ads-metrics) with `scope=ad` and an explicit window.
* `name` defaults to the platform-native ad id (`externalId`) when Layers doesn't have a friendlier label cached.
* `adsContentId` is the bridge to [`ads-content`](/docs/api/reference/metrics/ads-content). When non-null, the ad was promoted from a Layers-scored creative; when null, the ad was created outside the Layers refresh loop.

## See also [#see-also]

* [`GET /v1/projects/:projectId/ads/adsets`](/docs/api/reference/ads/list-adsets) — parent adsets
* [`GET /v1/projects/:projectId/ads-content`](/docs/api/reference/metrics/ads-content) — scored creatives (join via `adsContentId`)
* [`GET /v1/projects/:projectId/ads-metrics?scope=ad`](/docs/api/reference/ads/ads-metrics) — detailed paid metrics for one ad
