The Play Publisher API cannot make an app’s first release live — only draft releases exist until you publish once by hand

I automated a Google Play release end to end: bundle uploaded, twenty-one store listings pushed,
screenshots in place, release notes attached. Then the last call failed with a 400 and a sentence I
had not seen in any of the docs I had read.

API error 400: Only releases with status draft may be created on draft app.

TL;DR — Until an app has been published once, the Android Publisher API accepts uploads,
listings and images, but refuses any release whose status is not draft. Country availability is
read-only in the API at every stage. The first go-live is a Console action; every later update is
fully automatable.

The setup

A small Android game, shipped as an app bundle. I drive Google Play from a script using the
Android Publisher API v3 with a service account: open an edit (a transaction), upload the
bundle, write the store listings, assign the bundle to a track (internal, alpha, beta,
production), commit. A track holds releases, and a release has a status: draft,
inProgress, halted or completed. completed means « serving to everyone on this track ».

The app existed in the Play Console — it had store listing text, a package name, a service account
with release permissions — but no version had ever gone live.

What I expected

That the API surface was uniform: if edits.bundles.upload works and edits.tracks.update works,
then setting the release status to completed and committing the edit would put the app on the
store. The permission model is per-service-account, not per-app-state, so I had no reason to think
otherwise. The API reference documents status as an enum with four values and says nothing about
one of them being conditional.

What actually happens

Everything up to the last step succeeds, and succeeds silently in a way that looks like progress.

edit = service.edits().insert(packageName=PKG, body={}).execute()
service.edits().bundles().upload(packageName=PKG, editId=edit["id"],
                                 media_body=aab).execute()          # 200
service.edits().tracks().update(
    packageName=PKG, editId=edit["id"], track="production",
    body={"releases": [{"versionCodes": ["18"], "status": "completed"}]},
).execute()
service.edits().commit(packageName=PKG, editId=edit["id"]).execute()
# googleapiclient.errors.HttpError: 400
# "Only releases with status draft may be created on draft app."

Change "completed" to "draft" and the same sequence commits without complaint. The bundle is
uploaded, the track shows it, and nothing is served to anyone.

Two other things behave differently on a never-published app, and both cost me time because they
look like bugs:

  • edits.countryavailability returned an empty object — no countries, restOfWorld unset. It is a
    read-only resource: there is no update method at any stage, so distribution countries can
    only be chosen in the Console. On a published app the same call returned 176 countries and
    restOfWorld: true.
  • The public store URL returns HTTP 404 throughout, and keeps returning 404 for a while after
    the first rollout, because review and propagation take their own time. A 404 is not evidence that
    the release failed.

Two panels comparing what the Android Publisher API accepts before and after an app has been published once. Before: uploads, listings, images and draft releases all succeed, but a completed release returns 400 and country availability reads as zero countries. After: completed releases, staged rollouts and halts all succeed.

The fix

There is no API-side fix. The sequence that works:

# 1. Automatable now, on a never-published app
#    upload the bundle and leave the release as a draft
python publish.py upload --file app.aab --track production --status draft --commit

# 2. In the Play Console, by hand, once per app:
#    - Countries / regions on the production track
#    - Content rating questionnaire
#    - Data safety form
#    - "Publish app"

# 3. Automatable forever after
python publish.py promote --source production --target production \
       --status completed --commit

Step 3 is the exact call that returned 400 before step 2. After the Console publication it
returned committed and submitted for review, and the track flipped to
production completed versionCodes=['18'].

Why it works

Google models an unpublished app as a distinct state — the Console calls it a draft app — and the
API refuses the one operation that would change that state. The reasoning is not stated in the
reference, but it is consistent: the first publication requires a content rating, a data safety
declaration and a country selection, none of which have a write surface in the API. Letting a
script flip a release to completed would let it bypass forms that Google requires a human to
answer.

The practical consequence for a CI pipeline: the first release of a new app can never be fully
automated
, and a pipeline that treats a 400 here as a transient failure will retry forever. Detect
the string and fail with a message that points at the Console. Everything after the first
publication — new bundles, staged rollouts with userFraction, halting a bad release, listing
updates in any number of locales — works from the API alone.

Worth knowing while you are in there: versionCodes on a track replaces the list rather than
appending to it, so promoting a new build silently stops serving the old one unless you list it
again.

What I did not test

Whether a closed testing track behaves the same on a draft app — I only exercised production. I
did not test whether an app that was published and later unpublished returns to the draft-app
state. I did not measure how long review took, only that the public URL still 404’d shortly after
the rollout committed. And this is one developer account: I cannot say whether accounts subject to
the closed-testing requirement for new personal developers see a different error first.

Facts

context: Automating Google Play releases with the Android Publisher API v3 and a service account
problem: Committing a release with status completed returns 400 "Only releases with status draft may be created on draft app" until the app has been published once
solution: Upload as a draft release, publish once from the Play Console, then promote to completed from the API forever after
verified_on: 2026-08-27
applies_to: [Android Publisher API v3, apps that have never had a live release]
does_not_apply_to: [apps already published once, where completed releases and staged rollouts work from the API]

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Retour en haut