ChatGPT Ads: Advertiser API · Last verified: AUG 2026

Chapter 47 - Insights and Reporting

Sacha Lefebvre, founder of Are You Visible
Sacha Lefebvre Founder, Are You Visible · LinkedIn

Insights are exposed at four scopes, all with the same response envelope:

  • GET /ad_account/insights
  • GET /campaigns/{campaign_id}/insights
  • GET /ad_groups/{ad_group_id}/insights
  • GET /ads/{ad_id}/insights

Pick the scope that matches the question. Account-level for portfolio overview, campaign-level for budget pacing, ad-group level for bid analysis, ad-level for creative performance.

Query parameters

ParameterTypeNotes
time_granularitystringdaily (one row per day) or none (one aggregated row over the time range).
aggregation_levelstringad_account, campaign, ad_group, or ad. Lets you query an account-level endpoint and group results by ad.
limitinteger1-10000.
beforestringPagination cursor.
afterstringPagination cursor.
time_rangesstring[]Filter by time. Format: {"type":"date_range","since":"2026-04-25","until":"2026-05-01"}.
filtersstring[]One or more filter expressions.
fieldsstring[]Which columns to return per row.
sortstring[]Sort expressions, e.g. {"field":"clicks","direction":"desc"}.

Available row fields (non-exhaustive)

FieldNotes
idRow identifier (encodes scope + time bucket).
start_time, end_timeBucket bounds (Unix seconds).
readable_timeHuman-readable bucket label, e.g. 2026-04-02.
timezoneBucket timezone (matches Ad Account timezone).
impressionsDelivered impressions.
clicksClicks.
spendSpend in account currency (decimal in responses, e.g. 42.75).
ctrClick-through rate.
cpcCost per click.
cpmCost per thousand impressions.
ad_id, ad_nameAd scope.
ad_group_id, ad_group_nameAd group scope.
campaign_id, campaign_nameCampaign scope.

Pass the fields you actually need in fields[]. Don’t pull every field by default, large queries with all fields can hit pagination quickly. Reporting still centers on delivery and cost buckets, even as ChatGPT Ads targeting expands across three platforms (PPC Land, 2026).

Example: best-performing ad in a campaign over a week

Find the top ad first.

curl -sS -G "https://api.ads.openai.com/v1/campaigns/cmpn_101/insights" 
  -H "Authorization: Bearer $OPENAI_ADS_API_KEY" 
  --data-urlencode 'time_granularity=none' 
  --data-urlencode 'aggregation_level=ad' 
  --data-urlencode 'limit=1' 
  --data-urlencode 'fields[]=ad_id' 
  --data-urlencode 'fields[]=ad_name' 
  --data-urlencode 'fields[]=clicks' 
  --data-urlencode 'fields[]=impressions' 
  --data-urlencode 'time_ranges[]={"type":"date_range","since":"2026-04-25","until":"2026-05-01"}' 
  --data-urlencode 'sort[]={"field":"clicks","direction":"desc"}'

Then pull a daily breakdown for that ad.

curl -sS -G "https://api.ads.openai.com/v1/ads/$AD_ID/insights" 
  -H "Authorization: Bearer $OPENAI_ADS_API_KEY" 
  --data-urlencode 'time_granularity=daily' 
  --data-urlencode 'fields[]=readable_time' 
  --data-urlencode 'fields[]=clicks' 
  --data-urlencode 'fields[]=impressions' 
  --data-urlencode 'fields[]=ctr' 
  --data-urlencode 'time_ranges[]={"type":"date_range","since":"2026-04-25","until":"2026-05-01"}'

Example: last 7 days of campaign performance

curl -sS -G "https://api.ads.openai.com/v1/campaigns/cmpn_101/insights" 
  -H "Authorization: Bearer $OPENAI_ADS_API_KEY" 
  --data-urlencode 'time_granularity=daily' 
  --data-urlencode 'fields[]=readable_time' 
  --data-urlencode 'fields[]=clicks' 
  --data-urlencode 'fields[]=impressions' 
  --data-urlencode 'fields[]=spend' 
  --data-urlencode 'time_ranges[]={"type":"date_range","since":"2026-04-25","until":"2026-05-01"}'

Sample response

{
  "object": "list",
  "count": 1,
  "data": [
    {
      "id": "start=1775088000:end=1775174400:entity_id=ad_501",
      "readable_time": "2026-04-02",
      "timezone": "UTC",
      "impressions": 15548,
      "clicks": 312,
      "spend": 42.75,
      "start_time": 1775088000,
      "end_time": 1775174400
    }
  ],
  "first_id": "start=1775088000:end=1775174400:entity_id=ad_501",
  "last_id": "start=1775088000:end=1775174400:entity_id=ad_501",
  "has_more": false
}

Closing the attribution loop

For accurate revenue reporting on a Shopify store, three things must be true at once:

  1. The Insights API is delivering delivery and cost metrics (this chapter).
  2. The pixel is firing on every relevant page (Chapter 36): page_viewed, contents_viewed, items_added, checkout_started, order_created.
  3. The Conversions API is dual-shipping the same conversion events from the Shopify backend with shared event_id values (Chapter 40), and oppref is captured at landing and forwarded.

Without all three, attribution drifts, and a percentage-of-revenue commercial model becomes undefendable.

The Insights endpoint tells you what you spent. The pixel + CAPI tell you what came back. Together they let you compute an honest CPA and ROAS. Either one alone gives you a partial picture that breaks under audit.

Reading the data into a warehouse

Every Insights response is paginated (first_id, last_id, has_more). For warehouse pulls. Looker Studio, BigQuery, Snowflake, etc., paginate until has_more is false, store rows keyed by id, and run incrementally on start_time to avoid re-pulling history. This is the same pattern the Advertiser API supports for every list endpoint, so the warehouse pipeline shape is uniform across resources.