02 Get your data in /Integrations
Google Play reviews
Send Google Play reviews of your Android app to Dimension Labs with the universal API.
- GP Google Play reviewsTickets, chats, calls, reviews
- ⟳ Your jobPull new · map fields · every 15 min
- { } Universal APIPOST /track · /trackhistorical
- Dimension LabsRead in full · enriched daily
This brings in the Google Play reviews of your app’s production version: each review as a customer message, with its star rating and device details, and your developer reply when there is one. You run it on your side with the universal API, using the Google Play Developer Reply to Reviews API.
What you need
Section titled “What you need”- A Google Play Console account that can invite users and grant permissions, and a Google Cloud project.
- A Google Cloud service account with the Reply to reviews permission in Play Console, and its credentials.
- A Dimension Labs API key for a new Universal API source. See Your API key.
1. Get API access in Google Play
Section titled “1. Get API access in Google Play”Google’s steps are in Getting Started and Reply to Reviews.
- Create a Google Cloud project, open the Google Play Developer API page in the Google Cloud Console and click Enable.
- In the Google Cloud Console, go to Service Accounts, click Create service account and follow the steps. Then open the service account, go to Keys, and add a JSON key. Keep the key file in a secrets manager; Google’s page links to its guide on OAuth 2.0 for server to server applications.
- In Play Console, go to Users & Permissions and click Invite new users. Enter the service account’s email address, grant the Reply to reviews permission, and click Invite user.
- Authorize with the
https://www.googleapis.com/auth/androidpublisherscope and send the access token asAuthorization: Bearer ACCESS_TOKEN.
2. Pull new reviews
Section titled “2. Pull new reviews”curl -H "Authorization: Bearer $PLAY_ACCESS_TOKEN" \ "https://androidpublisher.googleapis.com/androidpublisher/v3/applications/com.example.app/reviews?maxResults=100"reviews.listreturns reviews for production versions only, and only reviews that include a comment. A rating without a comment is not available.- Recent only: Google says the API returns only reviews created or modified within the last week. The first result is the most recently created or modified, so page until
lastModifiedis older than your cursor, then stop. - Pagination: the default is 10 per page and
maxResultsgoes up to 100. PasstokenPagination.nextPageTokenastokenfor the next page. - In
comments, the first entry is theuserComment. If you have replied, your reply is the second entry, asdeveloperComment. - A review title and body arrive together in
text, separated by a tab. - Quota (Google’s): 200
GETrequests per hour, per app, for the Reply to Reviews API.
3. Map the fields
Section titled “3. Map the fields”Each review is one incoming message under its own sessionId. Your developer reply is an outgoing message in the same session.
| Dimension Labs | Google Play | Notes |
|---|---|---|
text | userComment.text (incoming), developerComment.text (outgoing) | Replace the tab between title and body with a line break. Add translationLanguage=en to get translated text, with the original in originalText. |
userId | reviewId | Google says a user can write only one review per app, so the review ID also stands for the reviewer. Don’t send authorName. |
incoming | userComment or developerComment | true for userComment, false for developerComment. |
sessionId | reviewId | Replace any colons. |
conversationId | reviewId | |
dimensionlabs_timestamp | lastModified.seconds | Unix seconds: toMillis turns them into milliseconds. A reply within a second of the review is moved a second later. |
platformJson | starRating, reviewerLanguage, appVersionName, appVersionCode, androidOsVersion, device, the package name | App version lets you compare reviews across releases. |
4. Send it
Section titled “4. Send it”Install the auth library in your project first: npm install google-auth-library. Instead of a key file on disk, the code reads the service account’s JSON key from an environment variable, which works the same on your computer, Lambda or Cloud Run.
// google-play-reviews.mjs. Environment: PLAY_SERVICE_ACCOUNT_JSON (the service account's JSON key, as text),// PLAY_PACKAGES (comma-separated package names, for example com.example.app), DL_API_KEY.import { sendToDimensionLabs, toMillis } from './dimensionlabs.mjs';import { GoogleAuth } from 'google-auth-library';
const HOUR = 60 * 60 * 1000;const sleep = (ms) => new Promise((r) => setTimeout(r, ms));const auth = new GoogleAuth({ credentials: JSON.parse(process.env.PLAY_SERVICE_ACCOUNT_JSON), scopes: ['https://www.googleapis.com/auth/androidpublisher'],});
// Uses a fresh access token on every call, and retries the quota (429) and server errors (5xx) with a growing pause.async function get(url) { for (let attempt = 0; ; attempt++) { const { token } = await (await auth.getClient()).getAccessToken(); const res = await fetch(url, { headers: { Authorization: `Bearer ${token}` } }); if (res.ok) return res.json(); if ((res.status !== 429 && res.status < 500) || attempt === 3) throw new Error(`Google Play returned ${res.status}: ${await res.text()}`); await sleep((Number(res.headers.get('retry-after')) || 2 ** attempt) * 1000); }}
async function syncApp(packageName, since, historical) { let token = '', newest = since, done = false; while (!done) { const q = new URLSearchParams({ maxResults: '100', ...(token && { token }) }); const page = await get(`https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${packageName}/reviews?${q}`); for (const r of page.reviews ?? []) { const user = r.comments?.find((c) => c.userComment)?.userComment; const dev = r.comments?.find((c) => c.developerComment)?.developerComment; if (!user) continue; const modified = toMillis(Number(user.lastModified?.seconds ?? 0)); if (modified < since) { done = true; break; } newest = Math.max(newest, modified); const id = r.reviewId.replace(/:/g, '-'); const base = { userId: id, sessionId: id, conversationId: id, platformJson: { packageName, rating: user.starRating, language: user.reviewerLanguage, appVersionName: user.appVersionName, androidOsVersion: user.androidOsVersion, device: user.device } }; const text = user.text?.replace(/\t/g, '\n').trim(); if (text) await sendToDimensionLabs({ ...base, text, incoming: true, dimensionlabs_timestamp: modified }, { historical: historical || modified < Date.now() - HOUR }); if (dev?.text?.trim()) { const replied = Math.max(toMillis(Number(dev.lastModified.seconds)), modified + 1000); await sendToDimensionLabs({ ...base, text: dev.text.trim(), incoming: false, dimensionlabs_timestamp: replied }, { historical: historical || replied < Date.now() - HOUR }); } } token = page.tokenPagination?.nextPageToken; if (!token) done = true; } return newest;}
// Sends every review created or modified at or after `since` (Unix ms), for each app. Returns the new `since`.export async function syncPlayReviews({ since, historical = false }) { let newest = since; for (const pkg of process.env.PLAY_PACKAGES.split(',').map((x) => x.trim()).filter(Boolean)) { newest = Math.max(newest, await syncApp(pkg, since, historical)); } return newest;}A review the user edits comes back with a newer lastModified and is sent again under the same sessionId; anything unchanged is a duplicate, and is removed automatically. One Dimension Labs source can hold all your apps: the package name goes in platformJson.packageName.
5. Run it
Section titled “5. Run it”Save the code as google-play-reviews.mjs in a project set up as in Set up your project, and run it with the scheduled sync runner: import { syncPlayReviews as sync } from './google-play-reviews.mjs';.
- Every 15 minutes: the scheduled job sends new reviews and replies to
/track, and they are enriched automatically every day. Anything more than an hour old when the job sees it goes to/trackhistorical, and needs a backfill. Four runs an hour, a page or two each, stay well inside Google’s quota of 200GETrequests an hour per app. - Keep it running: the API only reaches back one week, so a gap longer than that loses reviews.
- History: for anything older than a week, Google points to downloading reviews as a CSV file from Play Console: upload that file, check it first with the file checker, then run a backfill from the Backfill tab.
- Re-runs are safe: duplicates are removed automatically.
Google Play reference
Section titled “Google Play reference”Checked against Google’s documentation on 2026-10-13.