System design · Worked example
How to collect ChatGPT Ads reports
Build a read-only report of daily spend by campaign. Return the rows you received, their totals, and enough detail to tell whether the report is complete and current. Try the collector locally with fictional data. Live setup is in development and requires verified advertiser API access.
On this page
Use this guide to
- Turn a question about daily campaign spend into an Insights request.
- Collect the result in batches and show which rows, dates, or metrics are missing.
- Decide whether the result is ready for a decision or safe to pass to an agent.
Before you start
The examples use HTTP, the rules software follows to send a request over the web and receive a reply.
No account or API key is needed for this guide.
Decide what the report should cover
Start with results by campaign and day for one ad account you have permission to use. Choose the dates and metrics, then keep the account’s time zone, currency and the time you collected the data with the report. A live request needs separate Advertiser API access. Reporting definitions and data updates
- 01Request data from your server
- 02Collect results in batches
- 03Save data and progress
- 04Show results and gaps
Reports show totals for the campaigns or ads you ask about. Individual conversations, the ads considered for them and their ranking scores stay inside the platform. Keep the Ads API key, a secret your software uses to sign in, on your server. Authentication and account scope
Define the result before choosing the interface. Return the requested scope, the scope actually covered, and a status with the reason collection stopped. A subtotal should identify the rows it covers so the caller can tell whether it answers the original question.
Explore System designAsk Insights for daily campaign results
Insights is the part of the Ads API that returns performance reports. This request asks for one row per campaign per day, with the campaign ID, date, impressions, clicks and spend. Insights request reference
GET https://api.ads.openai.com/v1/ad_account/insights
aggregation_level=campaign
time_granularity=daily
fields[]=metadata.readable_time
fields[]=campaign.id
fields[]=campaign.impressions
fields[]=campaign.clicks
fields[]=campaign.spend
time_ranges[]={"type":"unix_range","start":1793491200,"end":1793577600}
# Query values must be URL-encoded.
# Authorisation is supplied by your server, never this page.This request covers 1 November 2026 in UTC, the time standard used for the example. The end time is midnight at the start of 2 November, so the request excludes that day. Check the account’s reporting time zone if you want a local calendar day. The download labels OpenAI’s request fields and our own report fields separately.
Check that you have every batch
| Interface | How to get the full result |
|---|---|
| General Insights | Insights sends results in batches called pages. Save first_id, last_id and has_more. If has_more is true, send the same request with after set to the last_id from that page. Stop when has_more is false. |
| Dedicated conversion report | A request to POST /conversions/insights can return error 413 if the report is too large. In that case you get no rows or marker for a next page. Split the request by campaign or other ID, or use shorter date ranges that do not overlap. |
| Local sample collector | Track the page markers already seen and the campaign and date pairs already accepted. Stop on a repeated marker or duplicate row and retain the earlier valid batches. |
Pagination means asking for the next page of the same result. Splitting an oversized conversion report means making several smaller requests and combining their results. Use the rules for the report you asked for. Reporting limits
Explore Algorithms & dataShow what your report is missing
{
"reference_kind": "illustrative-reporting-envelope",
"account_ref": "sample-account",
"status": "partial",
"coverage": {"completed_partitions": 2, "expected_partitions": 3},
"missing_partitions": ["campaign-group-c"],
"stop_reason": "retry_budget_exhausted"
}In this example, two of three batches are ready and the remaining batch is named. Give temporary failures a bounded retry budget. If access is denied or the results cannot be validated, stop and return the batches you already accepted with the reason the report is incomplete.
To resume across runs in production, save the original request, accepted batches, and the next cursor. Use the campaign and date to avoid counting a row twice within this report. When figures arrive late, reconcile the newer snapshot for the same scope instead of adding both snapshots together.
Explore Software & productionCan you use this report?
Prepared examples · Fictional data
The request in each example
Get daily impressions, clicks, spend and credited conversions for campaigns in a fictional UK ad account on 1 and 2 November 2026.
- Time zone and currency
- London time, GBP
- Conversion credit
- 30 days after a click or one day after a view. Results use the date of the ad interaction.
Open an example to see what came back and what you can do with it.
Complete reportThe final batch arrived
What came back
Both batches arrived, giving you three rows. Each row covers one campaign and day.
- Impressions
- 2,300
- Clicks
- 64
- Spend
- £36.00
- Credited conversions
- Total unavailable
One row shows zero conversions and another has no conversion count. The spend total covers all the returned rows, but there isn’t enough information to add up conversions.
What to do next
Use the spend total for the requested period. Record when you collected it and check for later updates before using recent figures to make a decision.
Empty reportThe request finished with zero rows
What came back
The first batch was empty and said there were no more batches. There are no totals to show.
No rows matched the request. That gives you no spend or conversion figures to interpret.
What to do next
Check the account, dates and filters. If those are right, report “no rows returned” and keep the totals blank.
Partial reportAnother batch is missing
What came back
The first batch returned two rows and a marker for the next batch. The next batch never arrived.
- Rows received
- 2
- Spend in rows received
- £30.50
£30.50 is the subtotal for the rows you have. The spend for the full period is still unknown.
What to do next
Keep the original dates and filters and use the saved marker to retry the missing batch. If you can’t recover it, label the report partial and carry the gap into any summary.
Stale reportAll batches arrived, but the figures are old
What came back
At a review on 5 November, the team still had a complete report collected on 3 November. They refreshed the same dates and received revised figures.
| Metric | 3 Nov, 09:00 | 5 Nov, 09:00 |
|---|---|---|
| Spend | £36.00 | £36.30 |
| Credited conversions | Unavailable | 3 |
Receiving every batch tells you the collection finished. It doesn’t tell you whether the platform has since updated the figures.
What to do next
Refresh the same request before using the old copy. Use the newer figures for the same dates, and retain both collection times so readers can see what changed.
Rejected requestThe requester lacked access
What came back
In this example, the reporting tool refused the request because the signed-in person didn’t have permission for the selected account. It returned no report.
The tool stopped at its access check. There are no figures to interpret for that account.
What to do next
Check that you selected the right account and ask an account admin to confirm your access. Retry once the account or permission has been corrected.
Interpret credited conversions
Before comparing reports, check that the account, dates, time zone, currency, and attribution settings match. Check coverage and freshness too. An unavailable conversion count represents missing information. A newer snapshot may have a different count for the same dates.
An attributed conversion is an action the platform credits to an ad under its reporting rules. To estimate how many additional sales the campaign caused, define the outcome and compare it with a credible estimate of what would have happened without the campaign. A suitable holdout can provide that comparison.
Explore Measurement & MLGive an agent a bounded reporting tool
Give the tool one job: read a campaign report within an explicit scope. For a live design, the service should enforce account access and request limits. Return the scope, status, coverage, and freshness alongside the values so an agent can explain an incomplete result before recommending a decision.
The local MCP tool reads the bundled fictional reports. Its inputs select a scenario and a page limit. Try the partial scenario and check whether the agent can explain which totals are available and why collection stopped. The collector and adapter run instructions are below.
Explore AI engineeringReference downloads
Use OpenAI’s documentation for the exact requests your software can send. The Harness downloads use made-up examples to explain how you might model ad selection or collect reports.
- OpenAI · official · externalOpenAI Ads OpenAPI specification
OpenAI’s file listing the requests its advertiser API accepts and the data it returns. OpenAPI is the format used to describe those rules.
- Harness · worked example · v1.0.0Example reporting request and result · JSON
See which fields go into a request to OpenAI and how your own software could record results and missing data. The sample values are made up.
- Harness · reference · v1.0.0Agent reference notes
See where the files came from, how they fit together and how to use them with a coding agent.
Platform sources
Official sources used in this guide. Check your own ad account for the features you can access.
Build on this
Try the report collector
Local report collector Run locally · Sample data
Run the collector from a checkout with Node.js 20 or later. Compare a complete report with a partial run using fictional data. No account is needed.
Open run instructionsLocal reporting CLI and MCP tool Run locally · Sample data
Use Node.js 20.3 or later to run the CLI or connect a local MCP host. Both return the same report from fictional campaign data.
Open run instructionsConnect to a live account Planned build
A deployment walkthrough and verified account setup are in development.
A video walkthrough is planned. The written reference is available independently.
Continue exploring system design