Docs
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 creative in the Layers inventory. (ads-content still carries the deprecated organicScore field, always 0; it is not a quality signal.)

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
    stringoptional
    Restrict to ads promoting this creative. Pass the prefixed `adc_<UUID>` id exactly as returned by ads-content, top-performers or this endpoint. 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": "Spring UGC — US",
      "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": "Q3 retargeting — 18-34",
      "status": "paused",
      "effectiveStatus": null,
      "adsContentId": "adc_6f5d4c3b-2a19-4e87-b0c5-1d2e3f4a5b6c",
      "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 creative record), top-performers (cross-source ranking on observed metrics), and - for generated content - the source content_container. Use it to answer:

  • "Where is this creative running?" Query /ads?adsContentId=adc_6f5d4c3b...
  • "Is this ad's creative eligible, and how has it performed when paid?" Follow adsContentId to /ads-content (eligibility, adPerformance) and /top-performers.
  • "Is this ad's creative manually overridden?" Same path; override state is returned by ads-content.

adsContentId is the prefixed adc_<UUID> identity — the same string ads-content and top-performers return for that creative, and the form the ?adsContentId= filter above accepts. Carry it around unchanged; do not strip the prefix.

adsContentId is null only for ads that existed on the platform before Layers tracked them or for creatives outside the Layers inventory (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 is a human-readable label, never an identifier. Layers stores no name at the ad grain, so it carries the parent ad set's (Meta) or ad group's (TikTok) stored name, falling back to "Meta ad" / "TikTok ad" when that name is unset. The platform-native ad id is on externalId.
  • adsContentId is the bridge to ads-content. When non-null, the ad was promoted from a creative in the Layers inventory; when null, the ad was created outside the Layers refresh loop.

See also

On this page