DataLatte
AI Agent for Google Reviews: Auto-Reply Script with Real Examples
AI Automation

AI Agent for Google Reviews: Auto-Reply Script with Real Examples

June 13, 2026·Nataliia· 13 min read All posts
Responding to Google reviews matters. Businesses that respond to reviews convert 45% more profile viewers into website visitors. But manually writing personalized replies to every review — especially when you're running a salon or coffee shop — doesn't happen consistently.
This article gives you a complete Python script that reads new Google reviews, generates AI-personalized replies (using Groq's LLaMA 3.3), and posts them back to Google automatically. The script handles 4-5 star reviews, 1-3 star reviews, and neutral reviews differently. It never posts a generic "Thanks for your feedback!" response.

How the System Works

Google Business Profile API
        ↓
  Fetch new reviews (unanswered)
        ↓
  Classify: positive / negative / neutral
        ↓
  Generate personalized reply via Groq AI
        ↓
  Post reply via Google Business Profile API
        ↓
  Log to JSON (avoid double-replying)
This runs daily via GitHub Actions or a cron job. Total cost: free (Groq free tier + Google Business Profile API is free).

What You'll Need

  • Google Cloud project with My Business Business Information API and My Business Reviews API enabled
  • OAuth2 credentials (service account or user OAuth flow)
  • Groq API key (free tier: 14,400 requests/day on LLaMA 3.3)
  • Python 3.10+

The Complete Script

#!/usr/bin/env python3
"""
Google Reviews AI Reply Agent
Fetches unanswered reviews and posts AI-generated personalized replies.
"""

import os
import json
import urllib.request
import urllib.parse
from datetime import datetime
from pathlib import Path

# ── Config ────────────────────────────────────────────────────────────────────
GROQ_API_KEY      = os.environ.get("GROQ_API_KEY", "")
GROQ_MODEL        = "llama-3.3-70b-versatile"

GOOGLE_TOKEN_FILE = os.environ.get("GOOGLE_TOKEN_FILE", "google_token.json")
LOCATION_NAME     = os.environ.get("LOCATION_NAME", "")  # accounts/{id}/locations/{id}
BUSINESS_NAME     = os.environ.get("BUSINESS_NAME", "our business")
OWNER_NAME        = os.environ.get("OWNER_NAME", "")

REPLIED_LOG_FILE  = "replied_reviews.json"

# ── Token management ──────────────────────────────────────────────────────────
def get_access_token() -> str:
    """Refresh OAuth2 token using stored credentials."""
    token_data = json.loads(Path(GOOGLE_TOKEN_FILE).read_text())
    
    # If token is still valid, return it
    if token_data.get("access_token") and token_data.get("expiry"):
        expiry = datetime.fromisoformat(token_data["expiry"].replace("Z", "+00:00"))
        if expiry > datetime.now(expiry.tzinfo):
            return token_data["access_token"]
    
    # Refresh
    payload = urllib.parse.urlencode({
        "client_id":     token_data["client_id"],
        "client_secret": token_data["client_secret"],
        "refresh_token": token_data["refresh_token"],
        "grant_type":    "refresh_token",
    }).encode()

    req = urllib.request.Request(
        "https://oauth2.googleapis.com/token",
        data=payload,
        headers={"Content-Type": "application/x-www-form-urlencoded"},
        method="POST",
    )

    with urllib.request.urlopen(req) as resp:
        new_token = json.loads(resp.read())
    
    token_data["access_token"] = new_token["access_token"]
    Path(GOOGLE_TOKEN_FILE).write_text(json.dumps(token_data, indent=2))
    return new_token["access_token"]

# ── Fetch reviews ─────────────────────────────────────────────────────────────
def fetch_reviews(access_token: str) -> list[dict]:
    """Get reviews for the location that haven't been replied to yet."""
    url = f"https://mybusinessreviews.googleapis.com/v1/{LOCATION_NAME}/reviews"
    
    req = urllib.request.Request(
        url,
        headers={"Authorization": f"Bearer {access_token}"},
    )

    with urllib.request.urlopen(req, timeout=15) as resp:
        data = json.loads(resp.read())
    
    reviews = data.get("reviews", [])
    # Filter to reviews without a reply
    unanswered = [r for r in reviews if "reviewReply" not in r]
    
    print(f"Found {len(reviews)} total reviews, {len(unanswered)} unanswered")
    return unanswered

# ── Classify review sentiment ─────────────────────────────────────────────────
def classify_review(review: dict) -> str:
    rating = review.get("starRating", "FIVE")
    rating_map = {"ONE": 1, "TWO": 2, "THREE": 3, "FOUR": 4, "FIVE": 5}
    stars = rating_map.get(rating, 5)
    
    if stars >= 4:
        return "positive"
    elif stars == 3:
        return "neutral"
    else:
        return "negative"

# ── Generate AI reply ─────────────────────────────────────────────────────────
def generate_reply(review: dict, sentiment: str) -> str:
    reviewer_name = review.get("reviewer", {}).get("displayName", "")
    first_name = reviewer_name.split()[0] if reviewer_name else ""
    review_text = review.get("comment", "(no text provided)")
    rating = review.get("starRating", "FIVE")
    
    if sentiment == "positive":
        instructions = """
Write a warm, genuine thank-you reply to this positive Google review.
- Start with their first name if available
- Reference 1–2 specific things they mentioned (don't be vague)
- Express that we look forward to seeing them again
- Keep it under 100 words — longer replies feel like templates
- Sound like a real person, not a corporation
- Do NOT use: "We appreciate your feedback", "Thank you for taking the time", 
  "Your satisfaction is our priority", or any other generic phrases
"""
    elif sentiment == "negative":
        instructions = """
Write a professional, empathetic reply to this negative Google review.
- Acknowledge their experience without being defensive
- Apologize for falling short of expectations (even if you disagree with their account)
- Offer a specific next step: "Please call us at [number] or email us directly so we can make this right"
- Keep it under 120 words
- Do NOT make excuses, argue, or ask for the review to be changed
- The goal is to show OTHER readers that you handle problems professionally
"""
    else:  # neutral
        instructions = """
Write a friendly, conversational reply to this neutral Google review.
- Thank them genuinely (not generically)
- Address anything specific they mentioned — especially any constructive feedback
- Invite them to return and suggest what they might enjoy next time
- Keep it under 80 words
"""

    prompt = f"""You're the owner of {BUSINESS_NAME}. Reply to this Google review.

Reviewer: {first_name or 'Anonymous'}
Rating: {rating} stars
Review text: "{review_text}"

Owner name for signing off: {OWNER_NAME or 'The team'}

{instructions}

Write only the reply text. No quotes around it. No "Reply:" prefix."""

    payload = json.dumps({
        "model": GROQ_MODEL,
        "messages": [{"role": "user", "content": prompt}],
        "max_tokens": 200,
        "temperature": 0.6,
    }).encode()

    req = urllib.request.Request(
        "https://api.groq.com/openai/v1/chat/completions",
        data=payload,
        headers={
            "Authorization": f"Bearer {GROQ_API_KEY}",
            "Content-Type": "application/json",
        },
        method="POST",
    )

    try:
        with urllib.request.urlopen(req, timeout=20) as resp:
            result = json.loads(resp.read())
            return result["choices"][0]["message"]["content"].strip()
    except Exception as e:
        print(f"  ✗ AI generation failed: {e}")
        # Fallback replies
        if sentiment == "positive":
            return f"Thank you so much{', ' + first_name if first_name else ''}! We're thrilled you had a great experience and look forward to seeing you again soon. — {OWNER_NAME or 'The Team'}"
        elif sentiment == "negative":
            return f"We're sorry to hear about your experience. Please reach out to us directly so we can make this right. Your feedback is important to us. — {OWNER_NAME or 'The Team'}"
        else:
            return f"Thank you for sharing your experience{', ' + first_name if first_name else ''}! We appreciate your feedback and hope to see you again. — {OWNER_NAME or 'The Team'}"

# ── Post reply to Google ──────────────────────────────────────────────────────
def post_reply(review_name: str, reply_text: str, access_token: str) -> bool:
    """Post reply via Google Business Profile API."""
    url = f"https://mybusinessreviews.googleapis.com/v1/{review_name}/reply"
    
    payload = json.dumps({"comment": reply_text}).encode()
    
    req = urllib.request.Request(
        url,
        data=payload,
        headers={
            "Authorization": f"Bearer {access_token}",
            "Content-Type": "application/json",
        },
        method="PUT",
    )

    try:
        with urllib.request.urlopen(req, timeout=15) as resp:
            resp.read()
            print(f"  ✓ Reply posted to {review_name}")
            return True
    except Exception as e:
        print(f"  ✗ Failed to post reply: {e}")
        return False

# ── Main ──────────────────────────────────────────────────────────────────────
def main():
    replied_log = json.loads(Path(REPLIED_LOG_FILE).read_text()) if Path(REPLIED_LOG_FILE).exists() else {}
    
    print("Getting access token...")
    access_token = get_access_token()
    
    print("Fetching reviews...")
    reviews = fetch_reviews(access_token)
    
    replied_count = 0
    for review in reviews:
        review_name = review.get("name", "")
        
        if review_name in replied_log:
            continue  # Already handled this review
        
        reviewer = review.get("reviewer", {}).get("displayName", "Anonymous")
        rating   = review.get("starRating", "?")
        sentiment = classify_review(review)
        
        print(f"\nProcessing: {reviewer} ({rating} stars / {sentiment})")
        
        reply = generate_reply(review, sentiment)
        print(f"  Draft reply: {reply[:100]}...")
        
        success = post_reply(review_name, reply, access_token)
        
        if success:
            replied_log[review_name] = {
                "replied_at":  datetime.now().isoformat(),
                "reviewer":    reviewer,
                "rating":      rating,
                "sentiment":   sentiment,
                "reply_preview": reply[:100],
            }
            replied_count += 1
    
    Path(REPLIED_LOG_FILE).write_text(json.dumps(replied_log, indent=2))
    print(f"\n✓ Done. Replied to {replied_count} new review(s).")

if __name__ == "__main__":
    main()

Example AI-Generated Replies

5-star review from Sarah: "Best balayage I've ever had. Emma really understood what I wanted without me having to show a million photos."
AI reply: Hi Sarah! Emma will be so happy to hear this — she genuinely loves the consultation process. Getting the color right without needing a reference board is exactly what we aim for. See you at your next appointment!

2-star review from Mike: "Waited 20 minutes past my appointment time. Good cut but the wait was frustrating."
AI reply: Mike, you're completely right and we apologize — your time matters and 20 minutes is a real inconvenience. We've been working on improving our scheduling and this is a reminder we need to do better. If you'd like to give us another try, please book directly with us and mention this — we'll make sure you're in the chair on time.

3-star review (no name): "Decent coffee, nothing special. Service was friendly though."
AI reply: Thanks for stopping in! Friendly service is something we work hard at, so that means a lot. If you're up for it next time, ask about our single-origin pour-overs — it's a different experience from the standard drip. We'd love another chance to impress on the coffee side too.

Setting Up the Google Business Profile API

  1. Go to Google Cloud Console
  2. Create a project → Enable "My Business Reviews API" and "My Business Business Information API"
  3. Create OAuth2 credentials (Desktop app type)
  4. Run the OAuth flow once to generate google_token.json with a refresh token
  5. Get your Location Name: accounts/{account_id}/locations/{location_id} — visible in the GBP API Explorer

Frequently Asked Questions

Q: Will Google penalize me for using AI-generated review replies?
No. Google has no policy against AI-assisted review responses. The replies post as coming from the business owner, same as manual replies. What Google does care about is that responses are genuine, not spammy, and not keyword-stuffed. The AI prompts in this script explicitly avoid generic language — each reply should read as authentic. If you're unsure about a specific reply, check it before it posts by adding a DRY_RUN mode that prints replies without posting.
Q: What happens if the AI generates a bad reply to a negative review?
Add a dry-run mode for negative reviews. Change post_reply() to only auto-post for positive reviews, and instead log negative review drafts for manual approval. You can do this by checking if sentiment == "negative": print("DRAFT (manual review needed):", reply); continue. This is the safer approach when your business reputation is at stake.
Q: How do I get my Location Name for the API?
The format is accounts/{account_id}/locations/{location_id}. The easiest way: use the Google Business Profile APIs Explorer to list your locations. Alternatively, your location ID appears in the URL when you're in the Google Business Profile dashboard.
Q: Does this work for businesses with multiple locations?
Yes. Store a list of LOCATION_NAME values and loop through them. The script runs the same logic for each location. For different locations with different owners, pass different OWNER_NAME and BUSINESS_NAME values per location.
Q: Can I add a rule to never reply to reviews less than 3 days old?
Yes. Add a check in the main loop: parse review.get("createTime"), compare to datetime.now(), and skip if less than 72 hours old. This gives you time to catch any reviews you want to handle personally before the script processes them.

Automate Your Google Review Responses

DataLatte's AI Review Management Agent monitors and replies to Google reviews within 2 hours — personalised, on-brand, and flagging negatives to you first.
Want More Local Customers?
Nataliia at DataLatte runs data-driven local marketing campaigns for local businesses — coffee shops, salons, pet groomers, and fitness studios. Book a free 30-minute strategy call or explore Google Ads management.

Free for local businesses

Want this applied to your business?

I'll review your Google presence, local SEO, and ad accounts — and send you a specific action plan within 48 hours. No pitch, no pressure.

Want hands-on help?

See how DataLatte handles AI Agents & Automation for local businesses.

Learn more
Nataliia — local marketing expert
Nataliia

Local marketing strategist with 10+ years at global agencies — OMD, Dentsu, GroupM, and BBDO. Now helping small businesses get the same data-driven edge. Based in Europe, working with clients in the US, UK, Australia, and beyond.

About Nataliia

Want this applied to your business?

Let's review your current marketing setup together — free, no obligations.

Get Your Free Marketing Audit