Layers
Partner APIAPI referenceAds

GET /v1/projects/:projectId/ads/ads

List individual ads across Meta, TikTok, and Apple, with creative identifiers for joining back to ads-content.

View as Markdown
GET/v1/projects/{projectId}/ads/ads
Phase 1stable
Auth
Bearer
Scope
ads:read

Lists individual ads under the project, across platforms. Meta and TikTok ads are included; Apple Search Ads is intentionally excluded because ASA does not expose the same ad-level object. Each ad carries its parent adset and campaign and the adsContentId that lets you join back to ads-content when the ad was promoted from a Layers-scored creative.

Mutating ads (push / replace / prune creative, pause / resume / archive, etc.) requires ads:write:creative and ads:write:lifecycle and goes through the bucket-mode authority gate. Apple Search Ads has no ad-level entity (D36) — Apple-side lifecycle happens at the keyword / adgroup grain.

Path
  • projectId
    string (UUID)required
    Project to list within.
Query
  • adsetId
    string (UUID)optional
    Restrict to ads in one ad set.
  • campaignId
    string (UUID)optional
    Restrict to ads in one campaign.
  • adAccountId
    string (UUID)optional
    Restrict to ads on one ad account.
  • platforms
    string[]optional
    Restrict to one or more platforms.
    One of: meta_ads, tiktok_ads, apple_ads
  • adsContentId
    string (UUID)optional
    Restrict to ads promoting this creative. Useful for "where is this creative running?"
  • status
    string[]optional
    Filter by platform-native status.
    One of: active, paused, archived, deleted, in_review, rejected
  • cursor
    stringoptional
    Opaque pagination cursor. Forged or malformed cursors are rejected and treated as no cursor (first page).
  • limit
    numberoptionaldefault: 100
    Page size, 1–200.

Example request

curl "https://api.layers.com/v1/projects/prj_254a4ce1-f4ca-42b1-9e36-17ca45ef3d39/ads/ads?adsetId=adg_01HXG9&status=active" \
  -H "Authorization: Bearer lp_..."
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);
}
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"]

Response

200OK
{
  "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
}

Joining ads to creatives

Every ad in this response has at most one adsContentId. That field is the bridge to ads-content (the scored creative), 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_6f5d4c3b...
  • "What score does this ad's creative have?" Follow adsContentId to /ads-content?adsContentId=....
  • "Is this ad's creative manually overridden?" Same path; override state is returned by ads-content.

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

  • status is the partner-normalized status (one of active, paused, archived, deleted, in_review, rejected).
  • effectiveStatus, creative, sourceType, launchedAt: may be null when platform creative metadata is unavailable. Use the platform ad manager for platform-native effective status.
  • metricsSnapshot7d is reserved (always {} today). For paid performance, call GET /v1/projects/:projectId/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. 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

On this page