Back to Blog

Google Business Reviews API: Complete Integration Guide

Learn how to integrate the Google Business Reviews API to fetch, reply to, and manage reviews programmatically with TypeScript examples.

By

+8

Post everywhere. One API.

Try Free

The Google Business Reviews API enables developers to programmatically manage customer reviews across Google Business Profile locations. Whether you're building a reputation management dashboard, automating review responses, or aggregating feedback across multiple locations, this API provides the foundation for scalable review management.

This guide covers everything you need to integrate the GBP API into your application: from initial setup through OAuth authentication, fetching reviews, replying programmatically, and handling the inevitable edge cases that come with production deployments.

Google Business Reviews API Architecture

Introduction to Google Business Profile API

Google's business APIs have evolved significantly over the years. The current implementation uses a combination of API endpoints:

  • My Business Account Management API (mybusinessaccountmanagement.googleapis.com/v1): Manages accounts and permissions
  • My Business Business Information API (mybusinessbusinessinformation.googleapis.com/v1): Handles location data
  • My Business API v4 (mybusiness.googleapis.com/v4): Manages reviews and local posts (still active for these features)

The Google My Business API reviews functionality specifically lives in the v4 API. While Google has migrated many features to newer v1 endpoints, review management remains on v4 with no announced deprecation date.

Note: Google frequently updates their API structure. The v4 endpoints for reviews are stable, but always check the official documentation for the latest changes.

What You Can Do with the Reviews API

The GBP API supports these core review operations:

OperationHTTP MethodEndpoint
List reviewsGET/accounts/{id}/locations/{id}/reviews
Get single reviewGET/accounts/{id}/locations/{id}/reviews/{reviewId}
Reply to reviewPUT/accounts/{id}/locations/{id}/reviews/{reviewId}/reply
Update replyPUT/accounts/{id}/locations/{id}/reviews/{reviewId}/reply
Delete replyDELETE/accounts/{id}/locations/{id}/reviews/{reviewId}/reply

You cannot delete customer reviews through the API. Only the reviewer or Google (through policy violations) can remove reviews.

Setting Up Google Cloud Project

Before writing any code, you need a properly configured Google Cloud project with the correct APIs enabled.

Step 1: Create a Google Cloud Project

  1. Navigate to Google Cloud Console
  2. Create a new project or select an existing one
  3. Note your project ID for later use

Step 2: Enable Required APIs

Enable these APIs in your project:

# Using gcloud CLI
gcloud services enable mybusinessaccountmanagement.googleapis.com
gcloud services enable mybusinessbusinessinformation.googleapis.com
gcloud services enable mybusiness.googleapis.com

Or enable them through the Cloud Console:

  • My Business Account Management API
  • My Business Business Information API
  • Google My Business API

Step 3: Configure OAuth Consent Screen

  1. Go to APIs & Services > OAuth consent screen
  2. Select External user type (unless you have a Google Workspace organization)
  3. Fill in the required fields:
    • App name
    • User support email
    • Developer contact email
  4. Add the required scope: https://www.googleapis.com/auth/business.manage
  5. Add test users (required while in testing mode)

Step 4: Create OAuth Credentials

  1. Go to APIs & Services > Credentials
  2. Click Create Credentials > OAuth client ID
  3. Select Web application
  4. Add your authorized redirect URIs
  5. Save your Client ID and Client Secret
// Store these in environment variables
const config = {
  clientId: process.env.GOOGLE_BUSINESS_CLIENT_ID,
  clientSecret: process.env.GOOGLE_BUSINESS_CLIENT_SECRET,
  redirectUri: process.env.GOOGLE_BUSINESS_REDIRECT_URI,
};

OAuth 2.0 for Google Business

Google Business Profile requires OAuth 2.0 with offline access to obtain refresh tokens. This allows your application to access the API without requiring users to re-authenticate.

Generating the Authorization URL

function getGoogleBusinessAuthUrl(state?: string): string {
  const scopes = [
    'https://www.googleapis.com/auth/business.manage',
    'https://www.googleapis.com/auth/userinfo.profile',
    'https://www.googleapis.com/auth/userinfo.email'
  ];

  const params = new URLSearchParams({
    client_id: process.env.GOOGLE_BUSINESS_CLIENT_ID!,
    redirect_uri: process.env.GOOGLE_BUSINESS_REDIRECT_URI!,
    scope: scopes.join(' '),
    response_type: 'code',
    access_type: 'offline',
    prompt: 'consent', // Forces consent screen to ensure refresh token
  });

  if (state) {
    params.append('state', state);
  }

  return `https://accounts.google.com/o/oauth2/auth?${params.toString()}`;
}

The prompt: 'consent' parameter is critical. Without it, Google may not return a refresh token on subsequent authorizations.

Exchanging the Authorization Code

After the user authorizes your application, Google redirects them back with an authorization code:

interface TokenResponse {
  access_token: string;
  refresh_token?: string;
  expires_in: number;
  token_type: string;
  scope: string;
}

async function exchangeCodeForToken(code: string): Promise<TokenResponse> {
  const response = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      code,
      client_id: process.env.GOOGLE_BUSINESS_CLIENT_ID!,
      client_secret: process.env.GOOGLE_BUSINESS_CLIENT_SECRET!,
      redirect_uri: process.env.GOOGLE_BUSINESS_REDIRECT_URI!,
      grant_type: 'authorization_code',
    }),
  });

  if (!response.ok) {
    const errorText = await response.text();
    throw new Error(`Token exchange failed: ${response.status} ${errorText}`);
  }

  return response.json();
}

Note: Store the refresh token securely. It's only returned on the initial authorization (or when using prompt: 'consent').

Account and Location Structure

The GBP API uses a hierarchical structure: Users have Accounts, and Accounts contain Locations. You need both IDs to access reviews.

Fetching User Accounts

interface GBPAccount {
  name: string; // Format: "accounts/123456789"
  accountName: string;
  type: string;
  role: string;
  state: { status: string };
}

async function getAccounts(accessToken: string): Promise<GBPAccount[]> {
  const allAccounts: GBPAccount[] = [];
  let pageToken: string | undefined;

  do {
    const url = new URL('https://mybusinessaccountmanagement.googleapis.com/v1/accounts');
    url.searchParams.set('pageSize', '100');
    if (pageToken) {
      url.searchParams.set('pageToken', pageToken);
    }

    const response = await fetch(url.toString(), {
      headers: { Authorization: `Bearer ${accessToken}` },
    });

    if (!response.ok) {
      throw new Error(`Failed to fetch accounts: ${response.status}`);
    }

    const data = await response.json();
    if (data.accounts) {
      allAccounts.push(...data.accounts);
    }
    pageToken = data.nextPageToken;
  } while (pageToken);

  return allAccounts;
}

Fetching Locations for an Account

interface GBPLocation {
  name: string; // Format: "locations/987654321"
  title: string;
  storefrontAddress?: {
    addressLines: string[];
    locality: string;
    administrativeArea: string;
    postalCode: string;
    regionCode: string;
  };
  websiteUri?: string;
}

async function getLocations(
  accessToken: string,
  accountId: string
): Promise<GBPLocation[]> {
  const allLocations: GBPLocation[] = [];
  let pageToken: string | undefined;

  do {
    const url = new URL(
      `https://mybusinessbusinessinformation.googleapis.com/v1/${accountId}/locations`
    );
    url.searchParams.set('readMask', 'name,title,storefrontAddress,websiteUri');
    url.searchParams.set('pageSize', '100');
    if (pageToken) {
      url.searchParams.set('pageToken', pageToken);
    }

    const response = await fetch(url.toString(), {
      headers: { Authorization: `Bearer ${accessToken}` },
    });

    if (!response.ok) {
      throw new Error(`Failed to fetch locations: ${response.status}`);
    }

    const data = await response.json();
    if (data.locations) {
      allLocations.push(...data.locations);
    }
    pageToken = data.nextPageToken;
  } while (pageToken);

  return allLocations;
}

Fetching Reviews with the Google Business Reviews API

With account and location IDs in hand, you can fetch reviews. The Google Business Reviews API returns reviews in reverse chronological order by default.

Basic Review Fetching

interface ReviewReply {
  comment: string;
  updateTime: string;
}

interface Review {
  id: string;
  name: string;
  reviewer: {
    displayName: string;
    profilePhotoUrl?: string;
    isAnonymous: boolean;
  };
  rating: number;
  starRating: 'ONE' | 'TWO' | 'THREE' | 'FOUR' | 'FIVE';
  comment?: string;
  createTime: string;
  updateTime: string;
  reviewReply?: ReviewReply;
}

interface ReviewsResponse {
  reviews: Review[];
  averageRating?: number;
  totalReviewCount?: number;
  nextPageToken?: string;
}

async function getReviews(
  accessToken: string,
  accountId: string,
  locationId: string,
  options?: { pageSize?: number; pageToken?: string }
): Promise<ReviewsResponse> {
  // Extract numeric IDs if full resource names are provided
  const accId = accountId.includes('/') ? accountId.split('/').pop() : accountId;
  const locId = locationId.includes('/') ? locationId.split('/').pop() : locationId;

  const pageSize = Math.min(options?.pageSize || 50, 50); // Max 50 per request
  const url = new URL(
    `https://mybusiness.googleapis.com/v4/accounts/${accId}/locations/${locId}/reviews`
  );
  url.searchParams.set('pageSize', String(pageSize));

  if (options?.pageToken) {
    url.searchParams.set('pageToken', options.pageToken);
  }

  const response = await fetch(url.toString(), {
    headers: { Authorization: `Bearer ${accessToken}` },
  });

  if (!response.ok) {
    const errorBody = await response.text();
    throw new Error(`Failed to fetch reviews: ${response.status} ${errorBody}`);
  }

  const data = await response.json();

  // Map star rating strings to numeric values
  const starRatingMap: Record<string, number> = {
    ONE: 1,
    TWO: 2,
    THREE: 3,
    FOUR: 4,
    FIVE: 5,
  };

  const reviews = (data.reviews || []).map((review: any) => ({
    id: review.reviewId || review.name?.split('/').pop(),
    name: review.name,
    reviewer: {
      displayName: review.reviewer?.displayName || 'Anonymous',
      profilePhotoUrl: review.reviewer?.profilePhotoUrl || null,
      isAnonymous: review.reviewer?.isAnonymous || false,
    },
    rating: starRatingMap[review.starRating] || 0,
    starRating: review.starRating,
    comment: review.comment || '',
    createTime: review.createTime,
    updateTime: review.updateTime,
    reviewReply: review.reviewReply
      ? {
          comment: review.reviewReply.comment,
          updateTime: review.reviewReply.updateTime,
        }
      : null,
  }));

  return {
    reviews,
    averageRating: data.averageRating,
    totalReviewCount: data.totalReviewCount,
    nextPageToken: data.nextPageToken,
  };
}

Fetching All Reviews with Pagination

For locations with many reviews, you'll need to handle pagination:

async function getAllReviews(
  accessToken: string,
  accountId: string,
  locationId: string
): Promise<Review[]> {
  const allReviews: Review[] = [];
  let pageToken: string | undefined;

  do {
    const response = await getReviews(accessToken, accountId, locationId, {
      pageSize: 50,
      pageToken,
    });

    allReviews.push(...response.reviews);
    pageToken = response.nextPageToken;

    // Add a small delay to avoid rate limiting
    if (pageToken) {
      await new Promise((resolve) => setTimeout(resolve, 100));
    }
  } while (pageToken);

  return allReviews;
}

Replying to Reviews Programmatically

The ability to reply to Google reviews API programmatically is essential for businesses managing multiple locations. Timely responses to reviews improve customer perception and can positively impact local SEO.

Creating a Review Reply

async function replyToReview(
  accessToken: string,
  reviewName: string,
  replyText: string
): Promise<{ success: boolean }> {
  // reviewName format: accounts/{accountId}/locations/{locationId}/reviews/{reviewId}
  const response = await fetch(
    `https://mybusiness.googleapis.com/v4/${reviewName}/reply`,
    {
      method: 'PUT',
      headers: {
        Authorization: `Bearer ${accessToken}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        comment: replyText,
      }),
    }
  );

  if (!response.ok) {
    const errorBody = await response.text();
    throw new Error(`Failed to reply to review: ${response.status} ${errorBody}`);
  }

  return { success: true };
}

Updating an Existing Reply

The same endpoint handles both creating and updating replies:

async function updateReviewReply(
  accessToken: string,
  reviewName: string,
  newReplyText: string
): Promise<{ success: boolean }> {
  // PUT request updates the existing reply
  return replyToReview(accessToken, reviewName, newReplyText);
}

Deleting a Reply

async function deleteReviewReply(
  accessToken: string,
  reviewName: string
): Promise<{ success: boolean }> {
  const response = await fetch(
    `https://mybusiness.googleapis.com/v4/${reviewName}/reply`,
    {
      method: 'DELETE',
      headers: {
        Authorization: `Bearer ${accessToken}`,
      },
    }
  );

  // 204 No Content indicates successful deletion
  if (!response.ok && response.status !== 204) {
    const errorBody = await response.text();
    throw new Error(`Failed to delete reply: ${response.status} ${errorBody}`);
  }

  return { success: true };
}

Reply Best Practices

When building automated reply systems, consider these guidelines:

ScenarioRecommended Action
5-star review with commentThank the customer, mention specific details from their review
5-star review without commentBrief thank you, invite them back
3-4 star reviewThank them, address any concerns mentioned
1-2 star reviewApologize, offer to resolve offline, provide contact info
Review with no reply after 24hPrioritize for immediate response

Build faster with Late

One API call to post everywhere. No OAuth headaches. No platform-specific code.

Free tier • No credit card • 99.97% uptime

Handling Review Updates and Deletions

Google allows customers to edit or delete their reviews. Your application should handle these scenarios gracefully.

Detecting Review Changes

The updateTime field indicates when a review was last modified:

interface ReviewChange {
  reviewId: string;
  changeType: 'new' | 'updated' | 'deleted';
  review?: Review;
}

async function detectReviewChanges(
  accessToken: string,
  accountId: string,
  locationId: string,
  previousReviews: Map<string, Review>
): Promise<ReviewChange[]> {
  const currentReviews = await getAllReviews(accessToken, accountId, locationId);
  const changes: ReviewChange[] = [];

  const currentReviewMap = new Map(
    currentReviews.map((r) => [r.id, r])
  );

  // Check for new and updated reviews
  for (const review of currentReviews) {
    const previous = previousReviews.get(review.id);

    if (!previous) {
      changes.push({ reviewId: review.id, changeType: 'new', review });
    } else if (previous.updateTime !== review.updateTime) {
      changes.push({ reviewId: review.id, changeType: 'updated', review });
    }
  }

  // Check for deleted reviews
  for (const [reviewId] of previousReviews) {
    if (!currentReviewMap.has(reviewId)) {
      changes.push({ reviewId, changeType: 'deleted' });
    }
  }

  return changes;
}

Webhook Alternative: Polling Strategy

Google doesn't provide webhooks for review changes. Implement a polling strategy instead:

async function pollForReviewChanges(
  accessToken: string,
  accountId: string,
  locationId: string,
  onNewReview: (review: Review) => Promise<void>,
  onUpdatedReview: (review: Review) => Promise<void>,
  onDeletedReview: (reviewId: string) => Promise<void>
): Promise<void> {
  let previousReviews = new Map<string, Review>();

  // Initial fetch
  const initialReviews = await getAllReviews(accessToken, accountId, locationId);
  previousReviews = new Map(initialReviews.map((r) => [r.id, r]));

  // Poll every 5 minutes
  setInterval(async () => {
    try {
      const changes = await detectReviewChanges(
        accessToken,
        accountId,
        locationId,
        previousReviews
      );

      for (const change of changes) {
        switch (change.changeType) {
          case 'new':
            await onNewReview(change.review!);
            previousReviews.set(change.reviewId, change.review!);
            break;
          case 'updated':
            await onUpdatedReview(change.review!);
            previousReviews.set(change.reviewId, change.review!);
            break;
          case 'deleted':
            await onDeletedReview(change.reviewId);
            previousReviews.delete(change.reviewId);
            break;
        }
      }
    } catch (error) {
      console.error('Error polling for review changes:', error);
    }
  }, 5 * 60 * 1000); // 5 minutes
}

Review Analytics and Insights

The GBP API provides aggregate metrics alongside individual reviews. Use these for dashboards and reporting.

Extracting Review Metrics

interface ReviewAnalytics {
  totalReviews: number;
  averageRating: number;
  ratingDistribution: Record<number, number>;
  repliedCount: number;
  unrepliedCount: number;
  replyRate: number;
  averageResponseTime?: number;
}

function analyzeReviews(reviews: Review[]): ReviewAnalytics {
  const ratingDistribution: Record<number, number> = {
    1: 0,
    2: 0,
    3: 0,
    4: 0,
    5: 0,
  };

  let totalRating = 0;
  let repliedCount = 0;

  for (const review of reviews) {
    ratingDistribution[review.rating]++;
    totalRating += review.rating;
    if (review.reviewReply) {
      repliedCount++;
    }
  }

  const totalReviews = reviews.length;
  const unrepliedCount = totalReviews - repliedCount;

  return {
    totalReviews,
    averageRating: totalReviews > 0 ? totalRating / totalReviews : 0,
    ratingDistribution,
    repliedCount,
    unrepliedCount,
    replyRate: totalReviews > 0 ? (repliedCount / totalReviews) * 100 : 0,
  };
}

Rate Limits and Quotas

The GBP API enforces rate limits to ensure fair usage. Plan your integration accordingly.

Current Rate Limits

Quota TypeLimit
Queries per minute300
Queries per day10,000 (default, can request increase)
Batch requestsNot supported for reviews

Implementing Rate Limiting

class RateLimiter {
  private requests: number[] = [];
  private readonly maxRequests: number;
  private readonly windowMs: number;

  constructor(maxRequests: number = 300, windowMs: number = 60000) {
    this.maxRequests = maxRequests;
    this.windowMs = windowMs;
  }

  async waitForSlot(): Promise<void> {
    const now = Date.now();
    this.requests = this.requests.filter((time) => now - time < this.windowMs);

    if (this.requests.length >= this.maxRequests) {
      const oldestRequest = this.requests[0];
      const waitTime = this.windowMs - (now - oldestRequest) + 100;
      await new Promise((resolve) => setTimeout(resolve, waitTime));
    }

    this.requests.push(Date.now());
  }
}

// Usage
const rateLimiter = new RateLimiter(300, 60000);

async function rateLimitedRequest<T>(
  requestFn: () => Promise<T>
): Promise<T> {
  await rateLimiter.waitForSlot();
  return requestFn();
}

Token Management and Refresh

Access tokens expire after one hour. Implement automatic token refresh to maintain uninterrupted access.

Token Refresh Implementation

interface TokenStore {
  accessToken: string;
  refreshToken: string;
  expiresAt: number;
}

class GoogleBusinessTokenManager {
  private tokenStore: TokenStore;
  private clientId: string;
  private clientSecret: string;

  constructor(
    initialTokens: TokenStore,
    clientId: string,
    clientSecret: string
  ) {
    this.tokenStore = initialTokens;
    this.clientId = clientId;
    this.clientSecret = clientSecret;
  }

  async getValidAccessToken(): Promise<string> {
    // Refresh if token expires in less than 5 minutes
    const bufferMs = 5 * 60 * 1000;
    if (Date.now() >= this.tokenStore.expiresAt - bufferMs) {
      await this.refreshAccessToken();
    }
    return this.tokenStore.accessToken;
  }

  private async refreshAccessToken(): Promise<void> {
    const response = await fetch('https://oauth2.googleapis.com/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: new URLSearchParams({
        refresh_token: this.tokenStore.refreshToken,
        client_id: this.clientId,
        client_secret: this.clientSecret,
        grant_type: 'refresh_token',
      }),
    });

    if (!response.ok) {
      const errorText = await response.text();
      throw new Error(`Token refresh failed: ${response.status} ${errorText}`);
    }

    const data = await response.json();

    this.tokenStore.accessToken = data.access_token;
    this.tokenStore.expiresAt = Date.now() + data.expires_in * 1000;

    // Note: Google may return a new refresh token; update if provided
    if (data.refresh_token) {
      this.tokenStore.refreshToken = data.refresh_token;
    }
  }
}

Error Handling Best Practices

Robust error handling is essential for production applications. The GBP API returns specific error codes that you can handle programmatically.

Comprehensive Error Handler

type ErrorType = 'refresh-token' | 'retry' | 'user-error' | 'unknown';

interface HandledError {
  type: ErrorType;
  message: string;
  shouldRetry: boolean;
  retryAfterMs?: number;
}

function handleGBPError(statusCode: number, errorBody: string): HandledError {
  const lowerBody = errorBody.toLowerCase();

  // Authentication errors
  if (
    lowerBody.includes('invalid_grant') ||
    lowerBody.includes('token has been expired or revoked')
  ) {
    return {
      type: 'refresh-token',
      message: 'Access token expired. Please reconnect your account.',
      shouldRetry: false,
    };
  }

  if (lowerBody.includes('invalid_token') || lowerBody.includes('unauthorized')) {
    return {
      type: 'refresh-token',
      message: 'Invalid access token. Please reconnect your account.',
      shouldRetry: false,
    };
  }

  // Permission errors
  if (lowerBody.includes('permission_denied') || lowerBody.includes('forbidden')) {
    return {
      type: 'user-error',
      message: 'You do not have permission to manage this location.',
      shouldRetry: false,
    };
  }

  // Resource not found
  if (lowerBody.includes('not_found')) {
    return {
      type: 'user-error',
      message: 'The requested resource was not found.',
      shouldRetry: false,
    };
  }

  // Rate limiting
  if (
    statusCode === 429 ||
    lowerBody.includes('rate_limit') ||
    lowerBody.includes('quota')
  ) {
    return {
      type: 'retry',
      message: 'Rate limit exceeded. Retrying after delay.',
      shouldRetry: true,
      retryAfterMs: 60000, // Wait 1 minute
    };
  }

  // Service unavailable
  if (statusCode === 503 || lowerBody.includes('service_unavailable')) {
    return {
      type: 'retry',
      message: 'Service temporarily unavailable.',
      shouldRetry: true,
      retryAfterMs: 30000,
    };
  }

  return {
    type: 'unknown',
    message: `Unexpected error: ${statusCode} ${errorBody}`,
    shouldRetry: false,
  };
}

Retry Logic with Exponential Backoff

async function withRetry<T>(
  operation: () => Promise<T>,
  maxRetries: number = 3
): Promise<T> {
  let lastError: Error | undefined;

  for (let attempt = 0; attempt <= maxRetries; attempt++) {
    try {
      return await operation();
    } catch (error: any) {
      lastError = error;

      const handled = handleGBPError(
        error.statusCode || 500,
        error.message || ''
      );

      if (!handled.shouldRetry || attempt === maxRetries) {
        throw error;
      }

      const backoffMs = handled.retryAfterMs || Math.pow(2, attempt) * 1000;
      console.log(`Retry attempt ${attempt + 1} after ${backoffMs}ms`);
      await new Promise((resolve) => setTimeout(resolve, backoffMs));
    }
  }

  throw lastError;
}

Using Late for Review Management

Building and maintaining a Google Business Reviews integration requires significant development effort: OAuth flows, token management, rate limiting, error handling, and ongoing API changes. [INTERNAL_LINK:api-integration-best-practices]

Late (https://getlate.dev) simplifies this entire process with a unified API that handles the complexity for you.

Why Choose Late for Review Management

Instead of managing OAuth tokens, handling rate limits, and dealing with API versioning yourself, Late provides:

  • Unified API: One consistent interface for Google Business Profile and other platforms
  • Automatic Token Management: Late handles token refresh and storage automatically
  • Built-in Rate Limiting: Never worry about hitting API quotas
  • Error Handling: Standardized error responses across all platforms
  • Webhook Support: Get notified of new reviews without polling

Getting Started with Late

import { Late } from '@getlate/sdk';

const late = new Late({
  apiKey: process.env.LATE_API_KEY,
});

// Fetch reviews with a single call
const reviews = await late.reviews.list({
  accountId: 'your-connected-account-id',
  platform: 'googlebusiness',
});

// Reply to a review
await late.reviews.reply({
  reviewId: 'review-id',
  message: 'Thank you for your feedback!',
});

Late's unified approach means you can expand to other review platforms (Yelp, Facebook, TripAdvisor) without rewriting your integration code.

Next Steps

  1. Sign up for Late at getlate.dev
  2. Connect your Google Business Profile accounts
  3. Start managing reviews programmatically with our unified API

Check out our documentation for complete API reference and additional examples.


Managing Google Business Profile reviews programmatically opens up powerful automation possibilities for reputation management. Whether you build directly on the GBP API or use a unified platform like Late, the key is implementing robust error handling, respecting rate limits, and maintaining fresh access tokens. The code examples in this guide provide a solid foundation for either approach.

Miquel Palet - Author

Written by

Miquel Palet

Founder & CEO

Miquel is the founder of Late, building the most reliable social media API for developers. Previously built multiple startups and scaled APIs to millions of requests.

View all articles

Learn more about Late with AI

See what AI assistants say about Late API and this topic

One API. 13+ platforms.

Ship social media features in minutes, not weeks.

Built for developers. Loved by agencies. Trusted by 6,325 users.