Most guides to Google Ads reporting are really guides to buying a reporting tool. Connect the account, pick a template, schedule the email. That works, and it skips the question that decides whether the report is any good: where does each number on the page actually come from?
It matters because the same figure can be right and wrong at once. Spend can be off by a factor of a million. A conversion count can be correct on Monday and higher on Thursday without anyone touching the campaign. A campaign that spent nothing can vanish from the report entirely rather than showing zero.
This guide maps every metric in a normal client report to the exact field that produces it, then covers the traps that quietly corrupt reports built on top of that data.
What goes in a Google Ads client report?
A client report answers three questions in order: what did we spend, what did it buy, and what should change. The metric set follows from that, and it is short. Everything else is detail a client did not ask for.
Business outcomes come first: conversions, cost per conversion, and conversion value where revenue is tracked. Delivery metrics come second, as the explanation: impressions, clicks, click-through rate, average cost per click. A breakdown by campaign, and where useful by keyword, shows which part of the account produced the result.
Which of those belong in a weekly note and which in the monthly report is a separate decision, covered in the agency KPI guide. This piece is about where the numbers come from once you have chosen them.
The source map: every metric and the field behind it
In the Google Ads API you select metric fields against a resource. Keyword-level data comes from the keyword_view resource, campaign and ad group data from the campaign and ad_group resources, and a date breakdown from the segments.date field. Google's own example query selects metrics.impressions, metrics.clicks, metrics.ctr and metrics.average_cpc from keyword_view.
| What the client sees | Field that produces it | Watch for |
|---|---|---|
| Impressions | metrics.impressions | Nothing unusual |
| Clicks | metrics.clicks | For non-Search campaigns this counts valid and invalid interactions |
| Click-through rate | metrics.ctr | Already a ratio, do not recompute it from rounded figures |
| Average cost per click | metrics.average_cpc | A money field, so it arrives in micros |
| Spend | metrics.cost_micros | Divide by one million before it goes anywhere near the report |
| Conversions | metrics.conversions | Counts only actions set to be included in the conversions metric |
| All conversions | metrics.all_conversions | The wider count, including actions excluded from the main metric |
| Conversion value | metrics.conversions_value | Meaningless unless values are configured on the conversion actions |
| Cost per conversion | metrics.cost_per_conversion | Inherits every caveat of the conversions field |
| Cross-device conversions | metrics.cross_device_conversions | Explains gaps between platform and analytics counts |
| Search impression share | metrics.search_impression_share | A ratio, not a percentage, and clamped at the low end |
| Search top impression share | metrics.search_top_impression_share | Same clamping behaviour |
| Lost impression share (rank) | metrics.search_rank_lost_impression_share | Clamped at the high end |
| Lost impression share (budget) | metrics.search_budget_lost_impression_share | Not selectable with ad_group in the FROM clause |
Written down like this, the report stops being a screenshot and becomes a specification. Anyone can check any figure by re-running one query, which is the property that matters when a client challenges a number six months later.
Trap one: money arrives in micros
Money fields in the Google Ads API are expressed in micros, meaning millionths of the account currency. Spend of one hundred pounds arrives as one hundred million, and to show currency you divide by one million. The naming is the tell: scan the metrics field reference and every monetary field carries the _micros suffix.
This sounds trivial and it is, right up until one field in one report is missed. The failure is loud when spend appears as a nine-digit number, and quiet when a derived figure such as cost per conversion is computed from a mix of converted and unconverted values. Convert at the point the number enters the report, once, and never again.
The field name carries the warning. Anything ending in _micros is not currency yet.
Trap two: impression share is clamped at both ends
Agency reports almost always carry a competitive visibility section: impression share, top-of-page rates, and how much was lost to rank or to budget. It is the part clients ask about, because it answers whether there is more to buy.
It also carries a reporting hazard that the same field reference states explicitly. Search impression share is returned in the range 0.1 to 1, and any value below 0.1 is reported as 0.0999. The lost-impression-share fields work the other way: they are returned in the range 0 to 0.9, and any value above 0.9 is reported as 0.9001.
So the extremes are censored. An account with genuinely tiny visibility cannot report it, because the floor answers first. An account losing almost everything to Ad Rank reports 0.9001 rather than the truth. Print those figures literally and the client reads a ceiling as a measurement.
Two consequences for the report. Say clamped, or say at least, when a value sits on a boundary rather than presenting it as exact. And remember these are ratios rather than percentages, so they need multiplying before display, which is the same class of unit error as micros.
One structural limit comes with them: the budget-lost field cannot be selected when ad_group is in the FROM clause. A visibility section broken down by ad group will be missing that column, and the honest move is to say so rather than silently omitting the row.
Trap three: yesterday's conversions change tomorrow
This is the one that damages client trust. Google Ads credits a conversion to the date of the click that led to it, not the date the conversion happened. Google calls the delay between the two conversion lag, and states plainly that because conversions are reported by that date, you may not always be seeing the most up-to-date numbers. Recent data is affected more than older data.
The consequence for reporting is specific. A report sent on the third of the month, covering the previous month, can understate conversions for the final days of that month. Re-run the identical query two weeks later and the figures are higher. Nothing broke, nobody edited anything, and the earlier report was never correct in the way the client assumed.
What to do about it
- State the date basis on the report itself. One line naming the range and the fact that conversions are credited to the click date removes the entire argument before it happens.
- Know that the alternative exists. The API also exposes conversion metrics on a conversion-date basis, where the date column means the date the conversion occurred rather than the click. Pick one basis deliberately and keep it stable, because switching mid-relationship makes every month look wrong.
- Leave a settling period before reporting where the client makes decisions on the numbers, rather than reporting on day one and correcting later.
If a figure does move after a report has gone out, that is a version question rather than a data question, and it is handled the same way as any other post-approval change: see the approval workflow guide for what has to be re-reviewed when a number changes.
Trap four: the rows that never arrive
Google's conversion reporting documentation states it directly: rows whose metrics are all zero are not returned. A campaign that spent nothing, a new conversion action with no data yet, a paused ad group, none of them appear in the response (conversion reporting).
A report that loops over whatever the API returned therefore shows a shorter list than the account contains, and the absence looks like tidiness rather than a gap. The client never learns that a campaign they asked about produced nothing, because it is simply not on the page.
The fix is to build the report from your list of campaigns, not from the API response. Start with what should be there, join the returned rows onto it, and render an explicit zero where nothing came back. A zero is information. A missing row is a question nobody knew to ask.
What data unavailable should look like
Sources fail. Authorisation expires, a request times out, an account is restructured mid-month. The report still has to go out, and the honest behaviour is per field rather than per report.
| Situation | Wrong response | Right response |
|---|---|---|
| A metric could not be retrieved | Leave the previous month's figure in place | Print data unavailable in that cell, with the reason |
| A campaign returned no rows | Omit it | Show it with zeros |
| Conversion data is still settling | Present it as final | Show it, and name the date basis |
| A whole source is down | Delay the report silently | Send it with the gap marked, and flag it to the account owner |
One more constraint worth knowing before promising a client something: custom columns built in the Google Ads interface cannot be retrieved through the API. If a client has grown attached to a custom column, it has to be recreated from its underlying fields rather than fetched.
Building it in the stack you already have
None of this requires new software. It requires the query, the field map above, and a template. The full architecture, meaning how figures get inserted rather than written and where the human stays, is covered in the automated client reporting guide. This spoke is the Google Ads layer of it.
The sequence that works: write the query for one client and one month, check three figures by hand against the interface, convert the money fields once, join the rows onto your campaign list, then let the model write commentary around the numbers it has been handed. The commentary is the only part that is generated.
What to remember
- Map every metric to its field before building anything. A report you cannot re-derive is a screenshot with extra steps.
- Divide money fields by one million, once, at the point of entry. Anything ending in _micros is not currency.
- Conversions are credited to the click date, so recent figures rise after publication. Name the date basis on the report.
- Rows with all-zero metrics are not returned. Build from your campaign list and render explicit zeros.
- When a source fails, print data unavailable in that field. Never carry forward a previous figure.
If you want the Google Ads report for your own clients built this way, in your own account and your own template, book a free mapping call. Thirty minutes, no commitment.








