How to create a Google Play service account for the Developer API

A Google Play service account is a robot Google account that your tooling authenticates as. Creating one takes two consoles, in this order: make the account and its JSON key in Google Cloud Console, then invite that account into Play Console and give it permissions. Skip the second half and everything authenticates perfectly and then fails with "The caller does not have permission."

That split is the single most confusing thing about this setup, so it is worth understanding before you start clicking.

Why it spans two consoles

The Google Play Developer API (its real name is androidpublisher) is a Google Cloud API. Identity for Google Cloud APIs comes from Google Cloud IAM, which is why the account is created there and why the credential is a Cloud-issued JSON key.

But your Play developer account is not a Google Cloud resource. It is a separate product with its own user list and its own permission model. Google Cloud can tell the API who is calling; only Play Console can say what that caller is allowed to do to your apps.

So: Cloud gives you an identity. Play Console gives that identity authority. Both are required, and neither implies the other.

Step 1: Enable the API

In console.cloud.google.com, pick a project or create one. The project is just a container for the key; the name does not matter and it does not need billing enabled.

Then go to APIs & Services → Library, search for Google Play Android Developer API, and click Enable.

If you forget this, calls fail with a 403 whose message names the project and says the API "has not been used in project N before or it is disabled". That message is unusually helpful. It includes a link that enables the API for you.

Step 2: Create the service account

IAM & Admin → Service Accounts → Create service account. Name it after what will use it, so a future you can tell three of them apart.

Google will then offer to grant it Google Cloud IAM roles. Skip that step. It needs no Cloud roles at all, not Viewer, not Editor, not Service Account User. Every permission that matters lives in Play Console, and a Cloud role here grants nothing towards your apps while widening what the key can reach inside your Cloud project.

This is the part people get wrong in the reassuring direction: they grant Editor, assume they are covered, and are then surprised the API still refuses.

Step 3: Create the JSON key

Open the service account you just created, then Keys → Add key → Create new key → JSON. A .json file downloads. That file is the credential.

Inside it, two fields matter to you:

  • client_email: something like appsubmit@my-app-123456.iam.gserviceaccount.com. This is the "user" you invite to Play Console.
  • private_key: an RSA private key. This is the secret; everything else in the file is metadata.

Like Apple's .p8, the private half is not recoverable. Unlike Apple's, you can create several keys for the same service account and delete them individually, so rotation is cheap: add a new key, deploy it, delete the old one. The service account and its Play permissions survive untouched.

The token your tooling exchanges this key for is short-lived (Google issues an OAuth 2.0 access token, typically valid for an hour) and requests the https://www.googleapis.com/auth/androidpublisher scope.

There is also a route through Play Console itself: Setup → API access at account level, which can link a Cloud project and create a service account without leaving the console. It ends up in the same place. Use whichever you find first.

Step 4: Invite the service account to Play Console

In play.google.com/console, open Users and permissions in the left sidebar, then Invite new users. Paste the client_email value from the JSON file into the email field.

It will look wrong. It is an address that cannot receive mail, at a domain you have never heard of, and Play Console will not send it an invitation it can accept. That is fine, service accounts are auto-accepted; the row simply appears in your user list.

Step 5: Grant permissions, which is the step people get wrong

You can grant at account level (every app on the developer account) or per app. Per app is the tighter choice and the one to prefer.

For a tool that manages listings and releases, the permissions that actually matter are:

  • View app information (read only): the baseline. Without it the account cannot see the app at all.
  • Reply to reviews: needed for anything that reads or answers reviews.
  • Manage store presence: in the Store presence section, described as "Edit your store listing and run store listing experiments…". This is the one most people miss. Without it, pushing a listing or screenshots fails with "the caller does not have permission".
  • Edit and delete draft apps: needed for apps that have never been published.
  • Release apps to testing tracks: internal, closed and open testing.
  • Release to production, exclude devices, and use Play App Signing: Google's exact checkbox name.

That last pair is the important structural fact: Play release permissions are per-track. Testing tracks and production are separate checkboxes, so a service account can happily deliver an internal build and then be refused when it tries to promote the same build to production. Same account, same app, same API call shape, different answer.

That is sometimes what you want. If you would rather the final production button stay a human act in the console, deliberately leave the production checkbox off and let the promotion fail, a refused API call is a clearer boundary than a policy nobody wrote down.

Step 6: Wait for it to propagate

Newly granted permissions do not take effect instantly. A verification attempt made seconds after saving often fails, then the identical attempt succeeds a few minutes later.

Usually a few minutes. Occasionally longer, Google's own guidance has cited up to 24 hours for permission changes to be fully reflected. So if you have just granted a permission and are getting a 403 on exactly the thing you granted, the correct next step is to wait and retry once before changing anything else. Re-granting, re-inviting or making a second key will not speed it up, and will leave you with more moving parts to debug.

Which mistake produces which error

The errors are distinguishable, which saves a lot of guessing.

401, invalid_grant or "Invalid Credentials": the credential itself is bad. The JSON was truncated or reformatted (the private_key field contains literal \n sequences that must survive intact), the key was deleted in Cloud Console, the service account was deleted, or the machine's clock is skewed. This never means a Play Console permissions problem.

403 with "has not been used in project … or it is disabled": Step 1 was skipped, or you enabled the API in a different Cloud project from the one that issued the key.

403 "The caller does not have permission": the service account is authenticating fine but is not in your Play Console user list, or is in it without the permission this call needs. Check which call failed: a listing or screenshot push wants Manage store presence; a release or promotion wants the matching Releases checkbox for that specific track.

404 on the app or a "no such package" style error: usually the package name is wrong, or the app has never had a build uploaded through the console. The Developer API cannot create an app from nothing; the first APK or AAB for a brand-new app still has to go through Play Console by hand.

401 or 403 immediately after a permission change: see propagation, above. Wait five minutes.

Keeping it safe

The JSON key is a full credential for whatever you granted it. Treat it accordingly: keep it out of the repository, store it in your CI provider's secret store rather than in a file on a build agent, and give each system its own key so revoking one does not break the others.

Rotation is genuinely easy here, which is a real advantage over Apple's model, delete a key in Cloud Console and it stops working immediately, while the service account and every Play permission you set up stay exactly as they were.

AppSubmit verifies the JSON against Google the moment you upload it and tells you which service account connected, and when a push later fails on a missing checkbox it names the checkbox rather than repeating Google's message, but the account is yours, in your Cloud project and your Play Console, and removing it is one click in the user list.

Last reviewed 2026-08-17. Apple and Google change their rules without notice, so check anything decision-critical against their live documentation.