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:

MetricKey in PostproxyMeaning
Search impressions, desktopbusiness_impressions_desktop_searchListing shown in Google Search on desktop
Search impressions, mobilebusiness_impressions_mobile_searchSame, on mobile
Maps impressions, desktopbusiness_impressions_desktop_mapsListing shown in Google Maps on desktop
Maps impressions, mobilebusiness_impressions_mobile_mapsSame, on mobile
All impressionsviewsThe four impression metrics summed by Postproxy
Website clickswebsite_clicksClicks on the website button
Call clickscall_clicksClicks on the call button
Direction requestsbusiness_direction_requestsClicks on Directions
Conversationsbusiness_conversationsMessage threads started from the listing
Bookingsbusiness_bookingsBookings made through the listing
Food ordersbusiness_food_ordersOrders placed through the listing
Menu clicksbusiness_food_menu_clicksClicks 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:

Terminal window
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:

Terminal window
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:

Terminal window
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.

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

StatusCause
400placement_id missing on a Google Business profile, or a from/to range over 366 days
404Unknown 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.

Ready to get started?

Start with our free plan and scale as your needs grow. No credit card required.