How to Pull Google Business Profile Insights via API
Read Search and Maps impressions, website clicks, call clicks, direction requests, bookings, and food orders for every Google Business Profile location as a timeseries — one endpoint, no Business Profile Performance API setup.
What Google exposes
Business Profile performance data comes from Google’s Business Profile Performance API, which replaced the Insights section of the old My Business API. It reports per location, per day, on these metrics:
| Metric | Key in Postproxy | Meaning |
|---|---|---|
| Search impressions, desktop | business_impressions_desktop_search | Listing shown in Google Search on desktop |
| Search impressions, mobile | business_impressions_mobile_search | Same, on mobile |
| Maps impressions, desktop | business_impressions_desktop_maps | Listing shown in Google Maps on desktop |
| Maps impressions, mobile | business_impressions_mobile_maps | Same, on mobile |
| All impressions | views | The four impression metrics summed by Postproxy |
| Website clicks | website_clicks | Clicks on the website button |
| Call clicks | call_clicks | Clicks on the call button |
| Direction requests | business_direction_requests | Clicks on Directions |
| Conversations | business_conversations | Message threads started from the listing |
| Bookings | business_bookings | Bookings made through the listing |
| Food orders | business_food_orders | Orders placed through the listing |
| Menu clicks | business_food_menu_clicks | Clicks on the menu |
Google counts impressions per unique user per day, so a customer who sees the listing three times in one day is one impression. Metrics that a location has never triggered (a bakery with no bookings) are absent rather than zero.
Not available anywhere in Google’s API, and therefore not in Postproxy: a follower count (Business Profiles have none) and per-post analytics for local posts.
Get the latest numbers for every location at once
GET /api/profiles/:id returns the newest snapshot for each location the profile manages, plus a sum across them:
curl "https://api.postproxy.dev/api/profiles/prof_abc123" \ -H "Authorization: Bearer YOUR_API_KEY"{ "id": "prof_abc123", "platform": "google_business", "latest_stats": [ { "placement_id": "accounts/113344/locations/558899", "recorded_at": "2026-09-16T03:10:22Z", "stats": { "views": 18432, "business_impressions_desktop_search": 2210, "business_impressions_mobile_search": 9871, "business_impressions_desktop_maps": 1104, "business_impressions_mobile_maps": 5247, "website_clicks": 612, "call_clicks": 288, "business_direction_requests": 941, "business_conversations": 37 } }, { "placement_id": "accounts/113344/locations/558900", "recorded_at": "2026-09-16T03:10:24Z", "stats": { "views": 7310, "website_clicks": 201, "call_clicks": 95, "business_direction_requests": 388 } } ], "summary_stats": { "recorded_at": "2026-09-16T03:10:24Z", "stats": { "views": 25742, "website_clicks": 813, "call_clicks": 383, "business_direction_requests": 1329, "business_conversations": 37 } }}Each snapshot is a trailing 30-day total as of recorded_at. summary_stats sums the numeric keys across every location, which is the number a multi-location dashboard’s headline tile wants.
Get the timeseries for one location
Snapshots are captured roughly every 23 hours and kept. The raw series is on GET /api/profiles/:id/stats with the location’s location_id as placement_id:
curl "https://api.postproxy.dev/api/profiles/prof_abc123/stats?placement_id=accounts/113344/locations/558899&from=2026-08-01T00:00:00Z" \ -H "Authorization: Bearer YOUR_API_KEY"{ "data": { "profile_id": "prof_abc123", "placement_id": "accounts/113344/locations/558899", "records": [ { "recorded_at": "2026-08-01T03:08:11Z", "stats": { "views": 16901, "website_clicks": 540, "call_clicks": 251 } }, { "recorded_at": "2026-08-02T03:09:40Z", "stats": { "views": 16988, "website_clicks": 544, "call_clicks": 255 } } ] }}placement_id is required for Google Business; a call without it returns 400. from and to are ISO 8601 timestamps.
Because each record is a rolling 30-day window, the series answers “how is the last month trending” directly. Two records 30 days apart are non-overlapping windows and can be compared as periods.
Get one record per day
For charts and period comparisons, daily_stats returns one clean record per calendar day:
curl "https://api.postproxy.dev/api/profiles/prof_abc123/daily_stats?placement_id=accounts/113344/locations/558899&from=2026-08-17&to=2026-09-15" \ -H "Authorization: Bearer YOUR_API_KEY"{ "data": { "profile_id": "prof_abc123", "platform": "google_business", "placement_id": "accounts/113344/locations/558899", "from": "2026-08-17", "to": "2026-09-15", "data_timezone": "America/Los_Angeles", "records": [ { "date": "2026-08-17", "views": 17244, "platform_stats": { "website_clicks": 548, "call_clicks": 259, "business_direction_requests": 902 } } ], "metrics": { "views": { "supported": true, "source": "native", "additive": false }, "followers": { "supported": false } } }}The range is capped at 366 days, and to defaults to yesterday. The metrics map tells a generic dashboard which keys to expect for this network without hard-coding platform rules.
History doesn’t start at the connection date. Google keeps about 18 months of performance data, and Postproxy pulls all of it on the location’s first daily sync, so a location connected today can chart the past year and a half straight away. Days older than Google’s window come back empty rather than as an error.
See which searches found the location
Search keywords show the queries that surfaced the listing in Google Search and Maps, month by month:
curl "https://api.postproxy.dev/api/profiles/prof_abc123/google_business/search_keywords?location_id=accounts/113344/locations/558899&from_month=2026-07&to_month=2026-09" \ -H "Authorization: Bearer YOUR_API_KEY"{ "months": [ { "month": "2026-07", "keywords": [ { "keyword": "coffee near me", "impressions": 245, "impressions_threshold": null }, { "keyword": "acme coffee market street", "impressions": null, "impressions_threshold": 15 } ] } ]}Without from_month / to_month you get the last three complete months. A request covers up to 12 months, within the 18 months Google keeps. Keywords come sorted by impressions; for low-volume searches Google gives only an upper bound, so impressions is null and impressions_threshold holds the bound. Branded searches (the business name) next to category searches (“coffee near me”) show how much of the listing’s traffic is discovery rather than people who already knew it.
Build a multi-location report
A weekly report for a chain or an agency’s client is a loop over placements, reading the latest snapshot for each:
const BASE = "https://api.postproxy.dev";const headers = { Authorization: `Bearer ${process.env.POSTPROXY_API_KEY}` };
const profile = await fetch(`${BASE}/api/profiles/${PROFILE}`, { headers }).then((r) => r.json());const { data: placements } = await fetch(`${BASE}/api/profiles/${PROFILE}/placements`, { headers }) .then((r) => r.json());
const names = Object.fromEntries(placements.map((p) => [p.id, p.name]));
const rows = profile.latest_stats .map(({ placement_id, stats }) => ({ location: names[placement_id], views: stats.views ?? 0, calls: stats.call_clicks ?? 0, directions: stats.business_direction_requests ?? 0, website: stats.website_clicks ?? 0, actions: (stats.call_clicks ?? 0) + (stats.business_direction_requests ?? 0) + (stats.website_clicks ?? 0), })) .sort((a, b) => b.actions - a.actions);
console.table(rows);actions per location divided by views is the listing’s conversion rate, the number most useful for spotting a location whose profile is being seen but not acted on.
Get notified instead of polling
A profile.stats webhook fires each time a new snapshot is recorded, carrying the profile ID and the placement. Subscribe to it to push the numbers into a warehouse as they land rather than scheduling a nightly pull.
Errors
| Status | Cause |
|---|---|
400 | placement_id missing on a Google Business profile, or a from/to range over 366 days |
404 | Unknown profile, or a placement_id the profile does not manage |
latest_stats is an empty array for a profile connected less than a day ago; the first snapshot lands within the first polling cycle. Full field tables are on the profile stats reference and the Google Business API reference.