1. Establish access and ownership

This architecture is for an application accessing a business profile it is authorised to manage. It is not a route for scraping arbitrary competitors. Google’s Business Profile API setup includes access prerequisites and OAuth; confirm the current requirements before building against a production account.

Keep authorisation separate from synchronisation. The user grants access through Google. Your server exchanges and stores tokens securely. The browser receives only the application data it needs, never a refresh token or client secret.

  • Identify the Google Cloud project and accountable owner.
  • Request the appropriate API access and configure the consent flow.
  • Use the least scope needed and verify which locations the signed-in user can access.
  • Define token revocation, disconnect and account deletion behaviour before launch.

2. Define the smallest useful data contract

If the product only needs review counts and average rating, avoid building a permanent archive of reviewer names and text. If text analysis is necessary, document why, who can access it and when it is deleted.

Keep upstream resource names intact. A display name is not a safe primary key. Use a provider identifier plus an internal business identifier so reconnections and multiple locations do not overwrite each other.

type LocationSnapshot = {
  businessId: string;
  providerLocationName: string;
  totalReviewCount: number;
  averageRating: number | null;
  observedAt: string;
  syncStatus: "complete" | "partial" | "failed";
};

3. Follow pagination to completion

The reviews list response includes review records, aggregate fields and an optional nextPageToken. The official method reference below defines the request and response. Read it alongside the current API access requirements.

A sync job should follow page tokens until none remains, enforce a maximum safety limit and record completion only after every page succeeds. A failed page must not make the system interpret unseen reviews as deleted.

// Server-side pseudocode. Keep access tokens out of browser code.
let pageToken;
const seenTokens = new Set();
const staged = [];
do {
  const page = await authorisedReviewsList({ location, pageToken });
  staged.push(...(page.reviews ?? []));
  pageToken = page.nextPageToken;
  if (pageToken && seenTokens.has(pageToken)) {
    throw new Error("Repeated pagination token");
  }
  if (pageToken) seenTokens.add(pageToken);
} while (pageToken);
await commitCompleteSnapshot(staged);

4. Give failures different responses

Do not log bearer tokens, full request headers or private review content. Log a correlation ID, business ID, operation, response category and timestamp. That is usually enough to diagnose a failed run without copying sensitive payloads into another system.

FailureHandling
Expired access tokenRefresh once server-side, then retry the request
Revoked permissionStop the job and ask the account owner to reconnect
Rate limit or transient server failureUse bounded backoff with jitter and respect Retry-After when present
Malformed dataQuarantine the record and preserve the last complete snapshot
Incomplete paginationMark partial; do not run deletion reconciliation

5. Commit a snapshot as a unit

Stage the fetched records, validate them and use a transaction to publish the completed snapshot. Make the sync idempotent: processing the same provider record twice should update it rather than create a duplicate.

If the feature stores individual reviews, distinguish a deleted upstream review from a failed read. Reconcile absence only after a complete authorised sync, following the provider’s applicable storage policies. A disconnect should disable scheduled jobs immediately.

BEGIN;
-- Upsert validated rows using provider location + review ID.
-- Publish aggregate snapshot and observed_at.
-- Reconcile absence only for a complete sync.
-- Mark job complete with a unique run ID.
COMMIT;

6. Test the operating conditions

Display when the data was last successfully refreshed. A cached result can still be useful if the interface is honest about its age. Never label a partial sync as live data. The Pressine calculator is intentionally manual and does not connect to this API.

  • Two pages return the same review: only one local record remains.
  • The second page fails: the previous complete snapshot remains available.
  • Permission is revoked: jobs stop and the UI requests reconnection.
  • A review is edited: the existing record updates with a new source timestamp.
  • Two jobs overlap: a lock or version check prevents stale writes.
  • The account disconnects: access is revoked and retention/deletion rules run.
Written by Pressine

Our studio combines design, software development and search. These guides document our approach and link to primary references where platform behaviour matters.

About our method