Ad Campaigns

Get campaign tree

Changed on

Shows your campaigns with their ad sets and ads nested inside, with spend and results rolled up at every level.

Uses a two-stage aggregation: ads are grouped into ad sets, then ad sets into campaigns. Metrics are computed over an optional date range, then rolled up from ad level to ad set and campaign levels. Pagination is at the campaign level. Ads without a campaign or ad set ID are grouped into synthetic "Ungrouped" buckets. If no date range is provided, defaults to the last 90 days. Date range is capped at 730 days max.

History: when an ad account connects, Zernio imports its last 90 days of metrics, so the metrics hold no data for days before that. When accountId or adAccountId resolves to a Meta ad account, a range reaching further back answers 202 with backfillPending: true and the covered part while the older days (up to 730 days back) are imported in the background; repeat the request until it returns 200. A request scoped to no Meta account never returns 202.

Pass timeIncrement=1 to also get a daily breakdown: each node gains a daily[] array of per-day metrics (same fields as the aggregated metrics) in the same call. Use dailyLevel (campaign default, or adset / ad) to choose which levels carry the series. This replaces calling the tree once per day for per-campaign daily trends.

Deleted objects stay in the tree. Deleting an ad or a campaign is a soft delete: the Ad documents move to status: cancelled and are kept indefinitely, so their historical spend still counts toward the metrics of any date range they fall in. There is no pruning job and no retention window. Filter on status if your view should hide them, but do that after reading the totals, not before.

get/v1/ads/tree

Request

  • Base URL: https://zernio.com/api
  • URL: https://zernio.com/api/v1/ads/tree
  • Auth: HTTP bearer

Query parameters

pageinteger

Page number (1-based)

limitinteger

Campaigns per page

source'zernio' | 'all'

all (default) returns both Zernio-created ads and those discovered from the platform's ad manager. Matches the web UI's default view. Pass zernio to restrict to isExternal=false only. Status is NOT filtered by default; use the status param for that.

platform'facebook' | 'instagram' | 'tiktok' | 'linkedin' | 'pinterest' | 'google' | 'twitter' | 'openai' | 'reddit'
status'active' | 'paused' | 'pending_review' | 'rejected' | 'completed' | 'cancelled' | 'error'

Filter by derived campaign status (post-aggregation)

adAccountIdstring

One or more platform ad account IDs to scope the tree to (agency profiles connect a whole Business Manager but a team usually cares about a subset). Comma-separate for multiple (?adAccountId=act_1,act_2,act_3); single value keeps its old shape. Max 50 accounts per request; the plural aliases adAccountIds and platformAdAccountIds are rejected with a 400 to stop them from silently returning the unfiltered fleet.

pageIdstring

Meta only: Facebook Page ID. Prunes the tree to ads whose creative is backed by this Page: campaigns and ad sets with no ad on the Page drop out, and rolled-up metrics cover only the Page's ads. Mirrors the same filter on /v1/ads and /v1/ads/campaigns.

accountIdstring

Account ID

profileIdstring

Profile ID

campaignIdstring

Restrict the tree to one or more campaigns by platform campaign id (the id the platform assigns, e.g. Meta's numeric campaign id). Comma-separate up to 100 ids (?campaignId=123,456). Filters the campaign set itself, so it works regardless of account size and pagination. Pass this when you already hold campaign ids (for example from an ad.status_changed webhook) instead of paging the whole tree.

updatedSincestring date-time

Return only campaigns with a change stored since this time (ISO 8601 with offset, e.g. 2026-09-30T10:00:00Z): a new ad, or a change to any ad's status, review status, name, budget or creative. Each matching campaign comes back whole (every ad set and ad). Metrics are not a change: to refresh numbers, filter with hasDelivery=true and a date range instead. Combines with every other filter (with hasDelivery/minSpend a campaign must match both).

searchstring

Case-insensitive substring match on campaign, ad set and ad names (_, % and spaces match literally), or an exact platform campaign, ad set or ad id. A campaign whose name matches returns with all its ad sets and ads; a match on an ad set or ad name returns only the matching branch. Filters the campaign set itself, so pagination.total counts only matching campaigns.

fromDatestring date

Start of the METRICS date range (YYYY-MM-DD). On its own it affects only the spend/impression numbers overlaid on each node, not which campaigns are returned. Pass hasDelivery or minSpend to also filter the campaign set to this window. Defaults to 90 days ago.

toDatestring date

End of metrics date range (YYYY-MM-DD). Defaults to today. Max 730-day range.

hasDeliveryboolean

Return only campaigns that delivered between fromDate and toDate: spend above zero, or impressions served at zero spend. Unlike status, which reads a campaign's CURRENT state, this filters on what happened inside the window, so a campaign that spent then and is paused today is still returned. Filters the campaign set itself, so pagination.total counts only matching campaigns.

minSpendnumber

Return only campaigns whose spend between fromDate and toDate reaches this amount. Expressed in each campaign's OWN currency (the currency field on the campaign node): spend is stored per ad account in its native currency and one response can span several. Implies hasDelivery; minSpend=0 applies no filter.

sort'newest' | 'oldest' | 'spend_desc' | 'spend_asc'

Campaign-level sort order. newest (default) / oldest order by the campaign's newest-ad createdAt. spend_desc / spend_asc order by aggregated spend in the requested date range; campaigns with no spend land at the end.

timeIncrement1

Set to 1 to also return a daily breakdown. Mirrors Meta Insights' time_increment=1: each node gains a daily[] array of per-day metrics (same fields as the aggregated metrics) alongside the range total, so you get per-entity daily trends in ONE call instead of calling the tree once per day. Only 1 (daily) is supported. The daily series covers the same date range and uses the same source data as metrics, except reach on Meta and TikTok: the range total is the platform's de-duplicated value, so daily reach does not sum to it. See dailyLevel to control which levels carry it.

dailyLevel'campaign' | 'adset' | 'ad'

Which tree levels get the daily[] series when timeIncrement=1. campaign (default) attaches it on campaign nodes only: the common per-campaign-trend case, and the smallest payload. adset adds it on ad sets too; ad adds it on every ad in ads[] as well (heaviest: a long range × up to 100 ads per ad set). Scope with campaignId to keep ad-level responses small. Ignored when timeIncrement is unset.

Response

Nested campaign tree with pagination

backfillPendingboolean

Meta only. Present and true while historical data is being backfilled.

Changes

    • info

      added the optional property /////////// to the response with the status

    • info

      added the optional property /////// to the response with the status

    • info

      added the optional property /////////// to the response with the status

    • info

      added the optional property /////// to the response with the status