How to Reply to Google Business Reviews via API
List and reply to Google Business Profile reviews programmatically — multi-location filtering, star-only reviews, webhooks for new reviews, and reply management.
Which Google API returns reviews?
Two different Google APIs return reviews, and only one of them can reply. A third category — third-party scraping APIs — is not Google at all, but shows up in the same search:
| Business Profile API | Places API | Scraping APIs | |
|---|---|---|---|
| Run by | Third parties | ||
| Whose reviews | Locations the authenticated account manages | Any place on Google Maps | Any place |
| How many | All of them | A small sample per place | Varies |
| Reply | Yes | No | No |
| Auth | OAuth as the location owner | API key | Vendor API key |
| Cost | Free | Metered by Google | Per request |
The question that picks your path is simply whether you own the locations. If you or your customers manage them, the Business Profile API is the only option that can reply, and it is what this guide covers. If you are gathering reviews for places you don’t own — competitor tracking, market research — neither Google API serves that well, which is why scraping vendors rank for this search.
Replying is the dividing line. Nothing outside the Business Profile API can post an owner response, no matter what it costs.
How reviews surface in the API
Google reviews live on a location, not on a post, so they flow through the Profile Comments API rather than the post-level Comments API. A review is a top-level comment; your answer is a reply nested under it.
Postproxy pulls reviews from Google twice a day (06:00 and 18:00 UTC) for every connected google_business profile, and fires a profile_comment.created webhook for each new one — so you don’t poll Google or manage its OAuth scopes.
Prerequisites
- A Postproxy API key
- A connected Google Business profile (the connected Google account may manage several locations)
List reviews
curl "https://api.postproxy.dev/api/profiles/PROFILE_ID/comments" \ -H "Authorization: Bearer YOUR_API_KEY"Results are newest-first by posted_at. Each review carries the reviewer’s name and avatar, your replies in a replies array, and the star rating in platform_data.star_rating.
The list is paginated — per_page defaults to 20 top-level reviews, and page is 1-based. To pull a location’s full review history, walk pages until one comes back short:
curl "https://api.postproxy.dev/api/profiles/PROFILE_ID/comments?per_page=100&page=2" \ -H "Authorization: Bearer YOUR_API_KEY"Two Google-specific shapes to handle:
- Star-only reviews — Google allows ratings with no text.
bodyisnullbutplatform_data.star_ratingis populated, so don’t skip records on an empty body. - Permalink — points at the location’s reviews page on Google, shared by all reviews there. Google’s API doesn’t expose per-review URLs.
What does a review return?
Each review comes back as a comment object. The fields you will actually branch on:
| Field | Type | What it holds |
|---|---|---|
body | string | null | Review text. null on star-only reviews |
platform_data.star_rating | integer | 1–5. Populated even when body is null |
platform_data.update_time | string | When the reviewer last edited it |
author_username | string | null | Reviewer display name. null on your own replies |
author_avatar_url | string | null | Reviewer profile image |
replies | array | Your reply, if one exists |
status | string | synced for incoming reviews |
posted_at | string | Sort key — the list is newest-first |
Reviews arrive with status: "synced"; the other statuses belong to replies you send. Because update_time moves when a reviewer edits, it is the field to watch if you cache reviews locally — posted_at will not change under you, but the text and rating can.
A review has no per-review URL. If you need to link a human to it, use the location’s metadata.place_id from the placements endpoint to build a Maps link, since Google does not return one.
Multi-location accounts
A google_business profile represents the connected Google account, which may manage multiple locations — including unrelated businesses. Without a filter, the list interleaves reviews from all of them.
Enumerate locations first, then scope by placement_id:
# List locationscurl "https://api.postproxy.dev/api/profiles/PROFILE_ID/placements" \ -H "Authorization: Bearer YOUR_API_KEY"
# Reviews for one locationcurl "https://api.postproxy.dev/api/profiles/PROFILE_ID/comments?placement_id=accounts/1234/locations/5678" \ -H "Authorization: Bearer YOUR_API_KEY"Reply to a review
curl -X POST "https://api.postproxy.dev/api/profiles/PROFILE_ID/comments" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "parent_id": "REVIEW_ID", "body": "Thanks Jane — glad the espresso landed. See you next week!" }'parent_id is required — Google reviews are user-generated, so top-level comments can’t be authored through the API (the request returns 422 without it). It accepts the Postproxy hashid or Google’s native resource path (accounts/X/locations/Y/reviews/Z).
The reply is stored immediately with status: "pending" and external_id: null, then pushed to Google by a background job. A 201 means Postproxy accepted it, not that Google did — the outcome shows up in the status:
| Status | Meaning |
|---|---|
pending | Accepted, not yet sent to Google |
published | Live on the review |
failed_waiting_for_retry | Google rejected the attempt; another is queued |
failed | Rejected and retries exhausted |
On either failure status, error carries a summary and error_details carries the structured platform response — platform_error_code, platform_error_subcode, platform_error_message, plus a postproxy_note with any extra context. Branch on the code rather than matching Google’s message text, which is not stable.
Two request-shape rules worth knowing before you hit them: parent_id is required and its absence returns 422, and replies are the only thing you can create here — a POST without parent_id is not treated as a new review, it is an error.
React to new reviews in real time
Subscribe to profile_comment.created on your webhook endpoint. It fires for newly synced incoming reviews and for your own replies once published. The payload includes placement_id, body, and platform_data.star_rating — enough to route on the spot: thank 5-star reviewers automatically, page a human for anything at 3 stars or below.
The event fires across all locations of the account; filter on data.object.placement_id if you only care about some. For a fully automated pipeline with an LLM drafting the responses, see How to Auto-Respond to Google Reviews with AI.
Update or remove your reply
Google supports one owner reply per review. To remove yours:
curl -X DELETE "https://api.postproxy.dev/api/profiles/PROFILE_ID/comments/REPLY_ID" \ -H "Authorization: Bearer YOUR_API_KEY"This deletes your reply only — businesses can’t delete the review itself; the original stays.
What the API can and can’t do
| Action | Supported |
|---|---|
| List reviews per location | Yes |
| Read star ratings (incl. star-only reviews) | Yes |
| Filter by location on a multi-location account | Yes — placement_id |
| Reply to a review | Yes |
| Delete your reply | Yes |
| Get a webhook on a new review | Yes — profile_comment.created |
| Create or delete a review | No — reviews are user-generated |
| Post more than one reply per review | No — Google allows one owner reply |
| Get a per-review permalink | No — Google returns a location-level URL only |
| Reply to reviews for a location you don’t manage | No — ownership is the gate |
Full object fields and error shapes: Profile Comments API. Publishing local posts, events, and offers to the same profile is covered on the Google Business platform page.