Campaign Reporting
Campaign endpoints are read-only: they return reporting data and accept no writes. All of them require the campaigns:read scope.
Stats are served from a cache that refreshes as engagement arrives. Use stats_refreshed_at to tell when the numbers were last refreshed, rather than assuming they’re live. A refresh doesn’t necessarily change the numbers.
Endpoints
Common patterns
- Listing: every list endpoint accepts
limit(0–500, default50),offset(default0), andstats(a boolean such astrue/falseor1/0;trueincludes the fullstatsobject on every campaign; defaultfalse). An out-of-rangelimitoroffset, or an invalidstatsvalue, returns422 Unprocessable Entity. Responses includeoffset,limit,total_items, anditems. - Campaign IDs: each item’s
idis the Hive campaign ID, used ascampaign_idin the stats endpoints. - Aggregates: counts and revenue are summed, and rates are recalculated from the summed counts rather than averaged. Filters are optional and can be combined;
campaign_idsis a comma-separated list of up to100IDs; duplicates are removed before the limit is checked. Datetime filters without a UTC offset are treated as UTC. Omitting all filters aggregates every campaign for the brand. Aggregatestats_refreshed_atis the oldest refresh timestamp among the contributing campaigns, so the whole total is at least that fresh. How campaigns without a refresh time are handled differs by channel; see each aggregate section. - Window boundaries: the
*_afterfilter is inclusive and the*_beforefilter is exclusive, so adjacent windows never double-count a campaign sent exactly on the boundary.
Call a list endpoint without stats, and compare each campaign’s stats_refreshed_at against
what you stored. Only request stats for campaigns whose timestamp changed.
Common errors:
404 Not Found(single campaign stats): no campaign with that ID exists for the authorized brand.422 Unprocessable Entity(aggregates): the*_beforefilter isn’t later than the*_afterfilter,campaign_idsis empty or has more than 100 distinct IDs, one or morecampaign_idsdon’t exist for the brand, or a datetime filter is malformed.
Email campaigns
List email campaigns
GET /campaigns/email/ lists sent email campaigns, most recently sent first. An A/B test appears once, as its parent campaign; its variants aren’t listed separately, and the parent’s stats and stats_refreshed_at combine all its variants.
Email campaign stats
GET /campaigns/email/{campaign_id}/stats/ returns engagement and revenue stats for one campaign.
A soft bounce is a delivery attempt the receiving server deferred, and it’s usually retried. A
recipient who soft bounces and is then delivered on a later attempt is counted in both
soft_bounced and delivered, so the two don’t sum to sent. Soft bounces are excluded from
bounced and bounce_rate; only bounced represents mail that will never arrive. Recipients
who soft bounce repeatedly are eventually reclassified as hard bounced.
stats is null for a campaign with no cached stats, and counts, rates, and revenue are
each null when that section hasn’t been computed. Engagement and revenue refresh
independently, so a campaign can return counts and rates while revenue is still null.
Aggregate email stats
GET /campaigns/email/stats/ totals stats across multiple campaigns. Filters: sent_after, sent_before (ISO 8601 datetimes, filtering on send time), and campaign_ids.
campaigns_included: number of campaigns that matched the filters. An A/B test counts once, though its stats come from its variants.stats_refreshed_at: the oldest refresh timestamp across the engagement and revenue stats of the contributing campaigns (and A/B variants). Stats with no recorded refresh time are skipped;nullif none has one.stats: same shape as single campaign stats, ornullwhen no matched campaign had stats.
SMS campaigns
List SMS campaigns
GET /campaigns/sms/ lists sent SMS campaigns, most recently sent first.
Fields match email campaigns, except there’s no subject, name is null if the campaign was never named, and num_recipients can be null.
SMS campaign stats
GET /campaigns/sms/{campaign_id}/stats/ returns engagement and revenue stats for one SMS campaign.
There’s no SMS equivalent of an email open, so clicks are the engagement signal and click_rate
is the headline number. For the same reason there’s no opened, bounced, or
spam_complained.
stats is null for a campaign whose stats have never been computed. Unlike email, the
sections are computed together, so once stats is present, counts, rates, and revenue are
all present. Zeroed revenue means no attributed orders, not missing data.
Aggregate SMS stats
GET /campaigns/sms/stats/ totals stats across multiple SMS campaigns. Filters: sent_after, sent_before (filtering on send time), and campaign_ids.
campaigns_included: number of campaigns that matched the filters, whether or not they had stats to contribute.stats_refreshed_at: the oldest refresh timestamp among campaigns with stats; campaigns whose stats were never computed are skipped.nullwhen no matched campaign had stats.stats: same shape as single campaign stats, ornullwhen no matched campaign had stats.
Ad campaigns
Hive serves these numbers from its cache of Meta’s campaign insights and doesn’t recompute them.
Unlike email and SMS, there’s no Hive-attributed order revenue for ads: purchase figures are
Meta’s own attribution, which is why they carry the meta_ prefix.
List ad campaigns
GET /campaigns/ads/ lists published Meta ad campaigns, most recently created first.
Ad campaign stats
GET /campaigns/ads/{campaign_id}/stats/ returns lifetime performance stats for one ad campaign.
stats is null for a campaign whose insights have never been cached. spend, impressions,
reach, frequency, clicks, and unique_clicks default to 0. Other metrics are null
when Meta reported nothing for them, which is common for purchase figures on campaigns without
conversion tracking.
Aggregate ad stats
GET /campaigns/ads/stats/ totals stats across multiple ad campaigns. Spend and counts are summed, and derived metrics are recalculated from those sums rather than averaged.
Filters: created_after, created_before (ISO 8601 datetimes), and campaign_ids. Unlike the email and SMS aggregates, which filter on send time, ad aggregates filter on when the campaign was created.
campaigns_included: number of campaigns that matched the filters, whether or not they had stats to contribute.stats_refreshed_at: the oldest refresh timestamp among the contributing campaigns, ornullif any of them has no recorded refresh time.stats: same shape as single campaign stats, ornullwhen no matched campaign had stats.
Someone reached by three campaigns is counted three times in the aggregate reach, because Meta
reports reach per campaign and the overlap between campaigns can’t be derived from those
figures.