Guide
How do I set up a repeatable multi-location Google Business Profile Performance API export?
Authenticate with the business.manage OAuth scope, enumerate every accessible account and location until each nextPageToken is gone, then repeat one daily-metrics request and one monthly-keyword request for each selected locations/{locationId} resource. Save the request dates, metric names, pagination state, and returned value types beside the data so the next run can be compared without changing what Google's response means.1,2,3,4
This is the operational layer behind the Google Business Profile Performance API overview. The API's daily and monthly methods are scoped to a location,1,3 so a multi-location export is a controlled loop over a location manifest, not one request containing every location.
After exporting, use the Google Business Profile performance comparison workflow to normalize the location rows and compare equivalent metrics.
What should I save for each export?
Treat the export as five linked records. The data file is not complete without the request manifest that explains its scope.
| Record | Save | Why it matters |
|---|---|---|
| Authentication | OAuth scope, token time, and the account used for discovery | The documented account, location, and Performance API methods require the business.manage scope.1,2,3 |
| Location manifest | Account resource, returned location resource, and a stable label when Google returns one | Performance requests use the exact locations/{locationId} resource name for an unobfuscated listing ID; keep any business label as metadata, not as the request path.1,3 |
| Daily request | Selected dailyMetrics, inclusive startDate, inclusive endDate, and the raw response |
A daily range includes both endpoints, and the multi-metric method still addresses one location at a time.1,4 |
| Monthly request | Inclusive startMonth, inclusive endMonth, pageSize, every pageToken, and the raw response |
Monthly keyword results aggregate over the requested months and can span multiple pages.3 |
| Validation record | Profile verification, associated-account access, available metrics, freshness check, and data scope | A missing card can mean that a metric does not apply. It is not automatically zero.5 |
Keep the raw response as well as any reporting table. A normalized table can make comparisons easier, but it must not erase whether Google omitted a zero daily value or returned a monthly threshold.
How do I authenticate and enumerate all accessible locations?
Use one OAuth authorization with the scope below, then build the manifest in two paginated passes.
https://www.googleapis.com/auth/business.manage
- Call
accounts.listfor the authenticated user. - Store every account returned, and call again with the returned
pageTokenuntilnextPageTokenis absent. Google says this list includes accounts the user owns and accounts where the user has management rights. The personal account is first unless it is filtered out.2 - For each account, call
accounts.locations.listwith areadMask. SetpageSizebetween1and100; the default is10. - Follow the location list's
nextPageTokenuntil it is absent. Do not treat the first response as the full location set.3 - Keep the returned location resource in the form
locations/{locationId}. The Performance API expects an unobfuscated listing ID in that resource name. If your system starts with another identifier, verify its mapping before calling the Performance API.1,3
The account type changes what the location list can return. For a PERSONAL account, Google returns only locations directly owned by that account. Other account types return all locations accessible through that account, directly or indirectly.3 Keep the account that produced each manifest row so the same location is not silently attributed to the wrong account when you reconcile multiple pages.
Which daily metrics should I request?
Choose the daily enums your report needs, and keep the enum names beside the result. The current Performance reference documents these metric families:
| Metric enum | Google-defined meaning |
|---|---|
BUSINESS_IMPRESSIONS_DESKTOP_MAPS |
Business impressions on Google Maps from desktop devices |
BUSINESS_IMPRESSIONS_DESKTOP_SEARCH |
Business impressions on Google Search from desktop devices |
BUSINESS_IMPRESSIONS_MOBILE_MAPS |
Business impressions on Google Maps from mobile devices |
BUSINESS_IMPRESSIONS_MOBILE_SEARCH |
Business impressions on Google Search from mobile devices |
BUSINESS_DIRECTION_REQUESTS |
Requests for directions to the business location |
CALL_CLICKS |
Clicks on the Business Profile call button |
WEBSITE_CLICKS |
Clicks on the Business Profile website |
BUSINESS_BOOKINGS |
Bookings made through Reserve with Google |
BUSINESS_FOOD_MENU_CLICKS |
Clicks to view or interact with menu content |
Google marks BUSINESS_CONVERSATIONS and BUSINESS_FOOD_ORDERS as deprecated in the same enum reference.4 Keep those labels visible if an old configuration still requests them, but do not add deprecated enums to a new default metric set without rechecking Google's current documentation.4
For reader-facing definitions of Views, Searches, calls, directions, bookings, and menu activity, use the Google Business Profile performance metrics guide. The API enum and the owner-facing report label are not always the same thing,4,5 so preserve both when your reporting layer maps one to the other.
How do I fetch daily data for every location?
Call locations.fetchMultiDailyMetricsTimeSeries once for each selected location. The method accepts repeated dailyMetrics query values, requires a dailyRange, and uses an empty request body. Its path contains one locations/* resource, so the loop over the manifest is what makes the export multi-location.1
If a separate job needs only one daily metric for a location, Google's locations.getDailyMetricsTimeSeries method is the single-metric alternative. Keep the same location resource and inclusive date-range record.6
For each location, persist these request fields before accepting the response:
location: locations/{unobfuscatedLocationId}
dailyMetrics[]: [the exact enum names requested]
dailyRange.startDate: YYYY-MM-DD
dailyRange.endDate: YYYY-MM-DD
requestBody: empty
Both startDate and endDate are inclusive. Store them next to every time series, not only in a job-level configuration, because the date range is part of what the value means.1,4
The response contains a metric, an optional dailySubEntityType, and a time series of date-value pairs. Google says the value field is not present when the value is zero.4 Normalize that deliberately:
valuepresent: store the returned integer.valueabsent in a returned date-value pair: store0with a state such asomitted_zero.- The metric or response is absent because the request failed or the response is incomplete: store an error or incomplete state, not zero.
dailySubEntityTypeabsent: keep it absent. Do not invent a breakdown.
This distinction protects the export from turning a valid zero into an unknown value or turning an incomplete response into a false zero.
How do I export monthly search keywords?
Run locations.searchkeywords.impressions.monthly.list separately for each location. Use the same locations/{locationId} resource from the manifest, set an inclusive startMonth and endMonth, and continue with nextPageToken until no token remains.3
The monthly range is month-granular. Google considers only the year and month fields, not a day within the month. pageSize defaults to 100 and cannot exceed 100.3
Store each keyword row with these fields:
| Field | Preserve it as |
|---|---|
searchKeyword |
Google's lower-cased string |
insightsValue.value |
The exact aggregate when Google returns the value arm |
insightsValue.threshold |
A threshold state, meaning the actual value is below the returned threshold |
startMonth, endMonth |
The inclusive months used for the request |
location |
The locations/{locationId} resource that was queried |
insightsValue is a union. It contains either the sum of unique users who used the keyword across the requested months or a threshold below which the actual value falls.3 A threshold is not an exact count. Keep the union arm in the file and render a threshold as “below [threshold]” rather than as that number.
How do I validate the file before I accept it?
Run these checks in order. The first failed check identifies what to repair before a comparison or dashboard consumes the file.
| Check | Pass condition | If it fails |
|---|---|---|
| Authorization | The token carries the business.manage scope |
Reauthorize before discovering accounts or calling Performance methods |
| Account discovery | Every account page was read until nextPageToken was absent |
Rebuild the account manifest; do not compare a partial account set |
| Location discovery | Every location page used a required readMask, a pageSize from 1 to 100, and all returned page tokens |
Rebuild the location manifest and retain the account type exception |
| Location identity | Every request uses the exact locations/{locationId} resource for an unobfuscated listing ID |
Stop the row and verify any identifier mapping before calling the Performance API |
| Daily range | Every daily request stores both inclusive dates and the exact metric enums | Reject the comparison until the request window is known |
| Daily response | Omitted daily value fields are marked as zero omissions, while failed or incomplete responses are not zero-filled |
Re-run or label the response incomplete |
| Monthly keywords | The month range is inclusive, nextPageToken is exhausted, and value remains distinct from threshold |
Rebuild the monthly result before aggregating it |
| Profile eligibility | The profile is verified and the authenticated user is signed in to the Google Account associated with it | Resolve access or verification before treating missing data as a metric result |
| Metric availability | The file records which metrics Google returned and does not fill an unavailable metric with zero | Mark the metric unavailable for that profile |
| Freshness | A new month's Searches data is allowed up to five days to appear after the month starts | Mark Searches as pending refresh inside that window |
| Data scope | The file says whether its performance data includes organic Search results and Google Ads | Preserve the scope before comparing files |
Google says Performance data is available only for verified Business Profiles, requires the associated Google Account, and shows only metrics that apply to the business. Google also says the Searches metric updates at the start of each month and can take up to 5 days to appear.5
Can I use Google's manual download as a cross-check?
Yes. Use it as a control check for profile selection and timeframe, not as proof that the spreadsheet and API response have identical columns or semantics.
In Business Profile Manager on a computer:
- Select the checkbox beside the profiles to compare.
- At the top right, click Actions, then Insights.
- Select the timeframe.
- Click Download Report.5
Save the selected profiles, timeframe, and downloaded filename beside the API export. Compare the profile set and date or month scope first. Keep the API's daily zero-omission state, monthly value versus threshold, and exact location resource names in the API record even when the manual file presents a different layout.
Sources
-
Google for Developers, “Method: locations.fetchMultiDailyMetricsTimeSeries,” last updated 2024-10-16, read 2026-10-12. Read the multi-metric method
-
Google for Developers, “Method: accounts.list,” last updated 2024-10-16, read 2026-10-12. Read the account-list method
-
Google for Developers, “Method: accounts.locations.list” and “Method: locations.searchkeywords.impressions.monthly.list,” last updated 2024-10-16, read 2026-10-12. Read the location-list method and read the monthly keyword method
-
Google for Developers, “Package google.mybusiness.performance.v1,” last updated 2026-10-05, read 2026-10-12. Read the Performance API RPC reference
-
Google Business Profile Help, “Understand your Business Profile performance & insights,” date not stated on page, read 2026-10-12. Read Google's performance guidance
-
Google for Developers, “Method: locations.getDailyMetricsTimeSeries,” last updated 2024-10-16, read 2026-10-12. Read the single-metric method
See where your profile stands
The audit shows how this applies to your own profile: it reads a verified Google Business Profile and scores it the same way every month.
Visibility Score
0 to 100
20 scored factors across profile completeness, performance, competitive position. Free, no signup, on any verified Google Business Profile.
No signup, no card. You get the score and the factors behind it.
Common questions
- Does fetchMultiDailyMetricsTimeSeries fetch every location in one request?
- No. The method bundles several daily metrics for one `locations/*` resource. A multi-location export repeats the request for each selected location.
- Is an absent daily `value` an API failure?
- Not necessarily. Google documents that the `value` field is omitted when the value is zero. Keep that zero omission separate from a failed or incomplete response.
- Is a monthly keyword threshold an exact count?
- No. A threshold says the actual value is below the returned threshold. Preserve it as a threshold instead of reporting the threshold as the keyword's exact count.
- Why is a monthly Searches value missing just after the month starts?
- Google says Searches updates at the start of each month and may take up to 5 days to appear. Check the refresh window before treating a missing monthly value as a broken export.