• Google Play
  • Backend
  • Node.js
  • Mobile apps

Google Play service account: API setup and 403 error fixes

Set up a Google Play service account for the Developer API, Reporting API and report exports, grant minimal permissions, and fix the common 403 errors.

Author
Extenify Team
Published
Reading time
12 min read
A row of black combination lock boxes hanging from a white metal gate

A Google Play service account is how a server, a CI job or a reporting script talks to Google Play without a person logging in. You need one to read and reply to reviews with the Developer API, pull crash and ANR rates from the Reporting API, download monthly reports from Cloud Storage, verify purchases, or publish builds. Setting it up takes ten minutes. Getting the first call to succeed can take a day, because two separate permission systems are involved and one of them is slow to catch up.

This guide walks through the current setup (no project linking required), the smallest Play Console permissions for each job, what to do when your organization blocks service account keys, a Node.js script that tests access to each API, and a table of the 403 errors developers hit most, with the fix for each.

Key takeaways

  • Two systems are involved. Google Cloud owns the service account, its key and the enabled APIs. Play Console decides what it may do, through Users and permissions. Cloud IAM roles do not grant Play access.
  • You "no longer need to link your developer account to a Google Cloud Project". Invite the service account's email address in Play Console like any user.
  • Grant the minimum: View app information (read-only) for reviews and vitals, Reply to reviews to answer them, and View app information and download bulk reports at the account level for report exports.
  • New permissions can take 24 to 36 hours to work. A 403 right after setup is often just that.
  • Organizations created on or after May 3, 2024 block service account key creation by default. Use an exemption or a keyless setup.

What the service account is used for

One service account can serve several Google Play interfaces, each with its own OAuth scope:

Job API or service OAuth scope
Read and reply to reviews, publish, verify purchases Google Play Android Developer API https://www.googleapis.com/auth/androidpublisher
Crash rate, ANR rate and other vitals Google Play Developer Reporting API https://www.googleapis.com/auth/playdeveloperreporting
Monthly reviews, statistics and financial reports Cloud Storage bucket pubsite_prod_rev_... https://www.googleapis.com/auth/devstorage.read_only

Google's getting started guide describes the current flow, and the Reporting API overview says the same thing for vitals: create a project, enable the API, create a service account, and invite it in Play Console. The old API access page and the project linking step are gone; Google states you "no longer need to link your developer account to a Google Cloud Project."

Step 1: Create a project and enable the APIs

In the Google Cloud console, create a project (or reuse one), open APIs & Services > Library, and enable what you need:

  • Google Play Android Developer API for reviews, publishing and purchases.
  • Google Play Developer Reporting API for vitals.

Cloud Storage access to the reports bucket needs no API to enable.

Enable the APIs in the same project that owns the service account. The API check happens against the project the credentials belong to, which is why a key from one project and APIs enabled in another fail. The Reporting API guide on this blog quotes a Stack Overflow answer where exactly that was the cause.

Step 2: Create the service account

Open IAM & Admin > Service Accounts, click Create service account, give it a clear name such as play-reports, and finish. You do not need to grant Cloud IAM roles for Google Play access. A developer on the Google developer forum who could not find any role for purchases.products.get "even in Roles" was looking in the wrong place (forum thread): Play permissions are set in Play Console in Step 4. Add Cloud roles only for other Cloud services you use, such as Pub/Sub for real-time notifications.

Copy the service account's email address. It looks like [email protected].

Step 3: Get credentials, with or without a key

The traditional route is a JSON key: open the service account, go to Keys > Add key > Create new key > JSON, and store the file in your secrets manager. Every Google client library picks it up from GOOGLE_APPLICATION_CREDENTIALS.

Many teams now hit a wall here. Google Cloud's security baseline is "enforced for all organizations created on or after May 3, 2024", and its first constraint is constraints/iam.managed.disableServiceAccountKeyCreation, which prevents "users from creating persistent keys for service accounts." If Create new key fails with an organization policy error, you have three options:

  1. Exempt one project. An organization policy administrator can turn the constraint off for a single project that only holds this service account. That keeps the rest of the organization protected.
  2. Use the service account without a key on Google Cloud. Code running on Cloud Run, Cloud Functions or Compute Engine can run as the service account directly, and the client libraries fetch tokens automatically.
  3. Use workload identity federation elsewhere. CI systems such as GitHub Actions can exchange their own identity token for short-lived Google credentials and impersonate the service account, as described in Google's workload identity federation docs. Calls still run as the service account email that Play Console knows.

If you do use a key, treat it as a production secret: one key per job, never in a repository, never in an app or extension bundle, and rotated on a schedule. Google's best practices for service account keys cover rotation and alternatives.

Step 4: Invite the service account in Play Console

In Play Console, open Users and permissions, click Invite new users, paste the service account email, choose permissions, and click Invite user. Play Console has account permissions, which "apply to all apps in the developer account", and app permissions, which "apply only to the apps you select", according to Google's permissions help page. Some permissions only exist at one level.

Grant only what the job needs. Google's Reporting API guide puts it this way: "It is best to grant the service account the minimal set of permissions needed to call the required API actions."

Job Play Console permission Level
Read reviews, read vitals View app information (read-only) App
Reply to reviews Reply to reviews App
Download reviews and statistics reports from Cloud Storage View app information and download bulk reports (read-only) Account ("Global")
Download financial reports View financial data, orders, and cancellation survey responses Account
Verify purchases and subscriptions View financial data, orders, and cancellation survey responses, plus Manage orders and subscriptions Account or app

The bulk reports row trips people up most. Google's reports help page says: "To access bulk reports, your 'View app information' permission must be set to 'Global.'" An app-level grant lets the API calls work while the bucket still returns 403.

Some troubleshooting guides, such as Codemagic's, suggest giving the service account Admin. It works, but it turns a leaked key into full control of your developer account. Start narrow and add one permission at a time.

Step 5: Test access with a script

Before you wire the account into real jobs, check what it can reach. This script asks each API for the smallest possible thing and prints which ones work. It uses google-auth-library:

npm install google-auth-library
// check-play-access.mjs: test what a Google Play service account can reach, one API at a time.
// Usage: GOOGLE_APPLICATION_CREDENTIALS=./play-sa.json node check-play-access.mjs com.example.app [pubsite_prod_rev_...]
import { GoogleAuth } from 'google-auth-library';

const [packageName, bucket] = process.argv.slice(2);
const auth = new GoogleAuth({
  scopes: [
    'https://www.googleapis.com/auth/androidpublisher',
    'https://www.googleapis.com/auth/playdeveloperreporting',
    'https://www.googleapis.com/auth/devstorage.read_only',
  ],
});

async function check(label, url) {
  try {
    const client = await auth.getClient();
    const res = await client.request({ url });
    console.log(`OK    ${label}`);
    return res.data;
  } catch (err) {
    const status = err.response?.status ?? 'network';
    const message = err.response?.data?.error?.message ?? err.message;
    console.log(`FAIL  ${label}: ${status} ${message}`);
    return null;
  }
}

const credentials = await auth.getCredentials();
console.log(`Service account: ${credentials.client_email}`);

const apps = await check('Reporting API: apps this account can see', 'https://playdeveloperreporting.googleapis.com/v1beta1/apps:search');
if (apps) console.log(`      ${(apps.apps ?? []).map((a) => a.packageName).join(', ') || '(no apps yet)'}`);

if (packageName) {
  await check(
    `Developer API: reviews for ${packageName}`,
    `https://androidpublisher.googleapis.com/androidpublisher/v3/applications/${packageName}/reviews?maxResults=1`,
  );
}

if (bucket) {
  await check(`Cloud Storage: reports bucket ${bucket}`, `https://storage.googleapis.com/storage/v1/b/${bucket}/o?prefix=reviews/&maxResults=1`);
}

The first check is the most useful. The Reporting API's apps.search method returns "The apps accessible to the user calling the endpoint", so an empty list means Play Console has not granted this service account anything yet, or the grant has not propagated. A failure on the Developer API alongside a working app list usually points to the Android Developer API not being enabled in the key's project.

We ran the script end to end with a generated key for a service account that does not exist. Every check failed with 400 invalid_grant: Invalid grant: account not found, which is the error you get when a key belongs to a deleted service account or was copied from somewhere else. With a real key, you should see OK for each API you set up.

Google Play service account errors and fixes

Error What it means Fix
"Google Play Android Developer API has not been used in project ... before or it is disabled" The API is off in the project that owns the credentials Enable it in that project, wait a few minutes, retry (Pugpig's guide shows the full message)
"The current user has insufficient permissions to perform the requested operation." Play Console has not granted this action for this app, or the grant has not propagated Check the permission for that app and action in Users and permissions, then wait up to 36 hours
"The caller does not have permission" (403, PERMISSION_DENIED) Wrong key file, or the service account was never invited in Play Console Compare client_email in the key with the invited address
invalid_grant: Invalid grant: account not found The service account was deleted, or the key is for another project Create a new key for an existing service account
Key creation fails with an organization policy error iam.managed.disableServiceAccountKeyCreation is enforced Exempt one project, or go keyless (Step 3)
"Package not found" The app has no first upload yet, or the package name is wrong Upload the first build manually in Play Console, and check the exact package name
403 on the pubsite_prod_rev_ bucket only The bulk reports permission is app-level, not account-level Grant View app information and download bulk reports at the account level
reviews.list returns nothing No written reviews were created or edited in the last 7 days Not an error; use the monthly reports for older reviews

Why it still fails after you fixed everything

The most frustrating part of the setup is the delay. Google's documentation does not give a propagation time, but service providers that onboard thousands of developers do. RevenueCat says it can take up to 36 hours for new Play service credentials to work with the Developer API, iaptic says up to 24 hours, and Codemagic says "Google may take 24-48 hours to propagate all access rights for all APIs or new users."

Two things help while you wait:

  • Retry before you change anything. Changing several things at once makes it hard to tell which change worked. Run the test script again a few hours later.
  • Try RevenueCat's workaround. It suggests opening your app in Play Console, going to the Monetize section, changing a product and reverting the change, which "should enable the new service credentials right away (or very shortly)." It is not documented by Google, so treat it as a tip, not a guarantee.

Putting the service account to work

Once the checks pass, the same credentials power several jobs on this blog:

The iOS side has its own key system and roles; the guide to the App Store Connect analytics API covers keys, JWTs and daily downloads.

A service account only reaches apps in your own developer account. To follow competitors, or to see ratings, reviews, installs and versions for every store in one place without managing keys, Extenify records public store listing data daily for Google Play, the App Store and Chrome, Edge and Firefox extensions, with alerts on every change. It covers Google Play app analytics for apps you do not own as well as your own. Every plan starts with a 7-day free trial, no credit card required; see pricing.

Google Play service account checklist

  • The Google Play Android Developer API (and the Reporting API, if needed) is enabled in the project that owns the service account.
  • The service account has no Cloud IAM roles it does not need.
  • Credentials are a key in a secrets manager, an attached service account, or workload identity federation.
  • The service account is invited in Play Console Users and permissions with the narrowest permissions per app.
  • Report exports have the account-level bulk reports permission.
  • apps:search lists the expected apps before any job goes live.
  • You waited up to 36 hours before changing a setup that looks correct.

Frequently asked questions

No. Google's getting started guide says you "no longer need to link your developer account to a Google Cloud Project." Create the service account in any project, enable the APIs there, and invite its email address in Play Console's Users and permissions page.

Why do I get "The current user has insufficient permissions to perform the requested operation"?

Play Console has not granted the service account the permission for that action on that app, or the grant is still propagating. Check the app's permissions in Users and permissions, then wait. New service credentials can take 24 to 36 hours to start working.

What permissions does a Google Play service account need to read reviews?

View app information (read-only) for the app covers reading reviews through reviews.list; Play Console's help says of Reply to reviews: "Users without this permission can view ratings and reviews but cannot reply to them." Add Reply to reviews if the account should answer them, and the account-level View app information and download bulk reports to download the monthly reviews CSV files.

How long does it take for a new Google Play service account to work?

Google does not publish a number. Providers report up to 24 hours (iaptic), up to 36 hours (RevenueCat) and 24 to 48 hours (Codemagic). Test with a simple call such as apps:search and retry a few hours later before you change the setup.

Can I use a Google Play service account without a JSON key?

Yes. On Google Cloud, run your code as the service account directly. Elsewhere, use workload identity federation to impersonate it with short-lived credentials. This is also the way around organizations that block key creation, which is the default for organizations created on or after May 3, 2024.

Summary

A Google Play service account lives in Google Cloud but gets its powers from Play Console. Enable the Play APIs in the project that owns the account, create credentials (a key if your organization allows it, otherwise keyless), and invite the account in Users and permissions with the narrowest permissions for each job, remembering that report exports need the account-level bulk reports permission. Then test with apps:search, read the error message carefully, and give new grants up to a day and a half before you start changing things.

Image credits

Put every store in one dashboard tonight.

Sign in with Google, paste one store link, and your unified view is live in under a minute. Then 7 days of Pro free, no credit card required, and you never touch your app's code.

Start free trial