GET /v1/projects/:projectId/ads/ads
List individual ads across Meta, TikTok, and Apple, with creative identifiers for joining back to ads-content.
/v1/projects/{projectId}/ads/ads- 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.
projectIdstring (UUID)requiredProject to list within.
adsetIdstring (UUID)optionalRestrict to ads in one ad set.campaignIdstring (UUID)optionalRestrict to ads in one campaign.adAccountIdstring (UUID)optionalRestrict to ads on one ad account.platformsstring[]optionalRestrict to one or more platforms.One of:meta_ads,tiktok_ads,apple_adsadsContentIdstringoptionalRestrict 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?"statusstring[]optionalFilter by platform-native status.One of:active,paused,archived,deleted,in_review,rejectedcursorstringoptionalOpaque pagination cursor. Forged or malformed cursors are rejected and treated as no cursor (first page).limitnumberoptionaldefault: 100Page 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
{
"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
adsContentIdto/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
statusis the partner-normalized status (one ofactive,paused,archived,deleted,in_review,rejected).effectiveStatus,creative,sourceType,launchedAt: may benullwhen platform creative metadata is unavailable. Use the platform ad manager for platform-native effective status.metricsSnapshot7dis reserved (always{}today). For paid performance, callGET /v1/projects/:projectId/ads-metricswithscope=adand an explicit window.nameis 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 onexternalId.adsContentIdis the bridge toads-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
GET /v1/projects/:projectId/ads/adsets- parent adsetsGET /v1/projects/:projectId/ads-content- creative inventory (join viaadsContentId)GET /v1/projects/:projectId/ads-metrics?scope=ad- detailed paid metrics for one ad