If you have ever clicked through the same Play Console release form for the tenth time in a week, you have felt the friction that the play console api was built to remove. The Google Play Developer API lets you script the parts of publishing that are repetitive, error-prone, or just tedious: creating releases, uploading bundles, managing tracks, and even updating in-app products — all from CI instead of a browser tab.
This guide covers what the API actually supports, how to authenticate, a working example that uploads an AAB, the common CI/CD patterns, and the pitfalls that catch most teams on their first integration.
What the Play Console API covers
The Google Play Developer API is a REST API (also exposed via official client libraries) that mirrors most of the release-management surface of the Play Console:
- Edits — a transactional unit of change. You open an edit, make changes (upload a bundle/APK, assign tracks, update listings), then commit. Nothing goes live until you commit.
- Tracks — internal, closed, open testing, and production. You can move releases between tracks programmatically.
-
Releases — staged rollouts with a
userFraction, release notes per locale, and halt/resume controls. -
In-app products and subscriptions — create and update SKUs, prices, and subscription base plans/offers via the
monetizationendpoints. - Reviews, voided purchases, and order management — pull reviews, check refunded/voided orders, and acknowledge purchases.
What it does not cover matters too: you cannot create the Play Console account itself, submit the identity verification, or appeal a policy decision through the API. Those flows are deliberately human and manual.
Authentication: service accounts and OAuth2
The API uses OAuth2 with a service account. The steps:
- In Google Cloud Console, create a project (or reuse your existing one) and enable the Google Play Android Developer API.
- Create a service account under IAM, download the JSON key file, and keep it somewhere safe — treat it like a password.
-
This is the step everyone misses: go to Play Console → Users and permissions → Invite new users, and add the service account's email address (
...@....iam.gserviceaccount.com). Grant it Release to production (or the appropriate subset) under Account permissions, plus access to the apps it should manage. Without this, every API call returns a 403 no matter how correct your credentials are.
The service account never logs into a UI; it authenticates with the JSON key and a short-lived OAuth2 token. Most client libraries handle token refresh for you.
Enabling API access in Play Console
Even with the Cloud Console side done, Play Console needs to know the API is allowed:
- Play Console → Setup → API access.
- Link your Google Cloud project.
- Confirm the service account appears under "Service accounts" with the right permissions.
If "API access" shows your project as unlinked, uploads will fail with cryptic permission errors. Link it before writing any code.
Example: create an edit and upload an AAB (Python)
The edits workflow is insert → mutate → commit. Here is a minimal example using google-api-python-client:
from google.oauth2 import service_account
from googleapiclient.discovery import build
SCOPES = ["https://www.googleapis.com/auth/androidpublisher"]
PACKAGE = "com.example.myapp"
creds = service_account.Credentials.from_service_account_file(
"service-account.json", scopes=SCOPES
)
service = build("androidpublisher", "v3", credentials=creds)
# 1. Open an edit (a transaction)
edit = service.edits().insert(body={}, packageName=PACKAGE).execute()
edit_id = edit["id"]
# 2. Upload the AAB
bundle = service.edits().bundles().upload(
packageName=PACKAGE,
editId=edit_id,
media_body="app/build/outputs/bundle/release/app-release.aab",
).execute()
print("Uploaded version code:", bundle["versionCode"])
# 3. Assign it to the internal track
track = service.edits().tracks().update(
packageName=PACKAGE,
editId=edit_id,
track="internal",
body={"releases": [{
"name": "CI build",
"versionCodes": [str(bundle["versionCode"])],
"status": "completed",
}]},
).execute()
# 4. Commit — nothing ships until this call
result = service.edits().commit(packageName=PACKAGE, editId=edit_id).execute()
print("Committed edit:", result["id"])
A curl version is possible but painful (multipart upload + OAuth2 headers), so use a client library in real pipelines.
Common CI/CD patterns
- Nightly internal builds: every merge to main builds an AAB and pushes it to the internal track. Testers get fresh builds without anyone touching the console.
-
Staged rollouts from CI: promote a closed-testing release to production with a
userFractionof 0.1, then bump the fraction in subsequent pipeline runs as crash metrics stay green. The API supportsstatus: "inProgress"withuserFraction, and halting a rollout is one call. - Release notes automation: generate notes from commit messages or your changelog file and set them per locale in the same edit.
- Multi-flavor publishing: loop over product flavors, uploading each AAB to its own track or app — something the UI makes tedious.
A typical GitHub Actions job: build with Gradle → sign → run the Python script above → post the track URL to Slack. Jenkins users do the same with a pipeline stage and a credentials binding for the JSON key.
Quotas and rate limits
The API has generous but real quotas (thousands of requests per day per app, varying by method). The edits endpoints are the ones to watch: each insert opens a transaction, and abandoned edits can pile up — always commit or delete them. Polling reviews.list or voidedpurchases.list in a tight loop is the classic way to burn quota; use reasonable intervals or RTDN webhooks where available.
Pitfalls that catch first-time integrators
- Service account not invited in Play Console. The #1 cause of 403s. The Cloud Console key alone is not enough.
- Wrong permission scope. The service account needs release permissions for the tracks you touch. "View app information" will not cut it for uploads.
-
Version code conflicts. The API rejects an upload whose
versionCodealready exists on any track. Make your CI bump version codes deterministically (e.g., from the build number). - Draft releases lingering. An uncommitted edit holds a lock on the track; a second pipeline run will fail until the first is committed or deleted. Add cleanup logic.
- Release notes required locales. Production releases may require notes for every locale your listing supports — generate or copy a default.
- The 14-day closed-testing rule. For new developer accounts, production access requires a 14-day closed test with enough testers. Automating uploads does not waive this; plan your pipeline around it.
When to get help
API automation pays off fastest once your release cadence outgrows manual clicking — but the initial setup (service accounts, permissions, track strategy) is where teams lose days. If you would rather have someone experienced set up the pipeline correctly the first time, Play Console publishing support can help with the account and release-side setup so your automation has a solid foundation to build on.
About the author: This guide was written by the team at Play Console Store, an independent marketplace helping Android developers with Google Play publishing — account setup, verification, and closed testing. Not affiliated with or endorsed by Google LLC.
Top comments (0)