Google Play release statuses: completed, inProgress, draft and halted
Google Play has exactly four release statuses that matter, and one of them is named badly enough to cause real confusion. Here is what each one means, taken from Google's own API reference:
draft: "The release's APKs are not being served to users."inProgress: "The release's APKs are being served to a fraction of users, determined by 'userFraction'."halted: "The release's APKs will no longer be served to users. Users who already have these APKs are unaffected."completed: "The release will have no further changes. Its APKs are being served to all users, unless they are eligible to APKs of a more recent release."
There is a fifth value, statusUnspecified, documented as "Unspecified status." You should never see it on a real release.
inProgress does not mean uploading
This is the one that catches people. inProgress is not "Google is processing your bundle". It means your release is live to a percentage of users and the rollout has not finished. A release can sit at inProgress for weeks if nobody raises the fraction.
Play Console shows this as a staged rollout with a percentage next to it. If you were expecting "rolled out to everyone" and you see inProgress at 10%, nothing is broken. Nobody finished the job.
completed is the finished state. Note the wording: "will have no further changes". Once a release is completed you cannot adjust its rollout fraction, because there is no fraction left to adjust.
The track ladder
Play has four standard tracks, and their API identifiers do not always match their console labels:
| API identifier | Play Console name | Who gets it |
|---|---|---|
internal |
Internal testing | A small named tester list, no review wait in practice |
alpha |
Closed testing | Testers you add by email or Google Group |
beta |
Open testing | Anyone who opts in, listing visible |
production |
Production | Everyone |
Closed testing tracks beyond the default alpha get custom names. Form factor tracks use a prefix, so a Wear OS production release is wear:production and an Automotive test track is automotive:qa.
Promotion up the ladder does not re-upload anything. The bundle already sits in Google's library once you have uploaded it, and promoting means assigning its versionCode to a higher track. That is why promotion is fast and upload is not.
What promotion does not skip is review. Google reviews releases on the reviewed tracks, and the API gives you no way to observe review progress. You get the release status, and that is all. Any tool that claims to show you a Play review queue position is inventing it.
Staged rollout, precisely
A staged rollout is a release with status inProgress and a userFraction between 0 and 1, exclusive. Set userFraction to 0.1 for 10%.
Two constraints worth knowing before you write the API call:
userFractioncan only be set when the status isinProgressorhalted. You cannot attach a fraction to acompletedrelease.countryTargetingrestricts a release to a set of countries using two-letter CLDR codes, and Google documents that this "applies only to inProgress releases in the production track". A staged rollout to a country subset is a production-only capability.
Increasing the fraction is a PUT to the track with a higher userFraction. Finishing the rollout means setting the status to completed and dropping the fraction.
There is no scheduled rollout. Google will not raise your percentage for you on a timer. Somebody or something has to make the call.
Halting a rollout
Setting a release to halted stops new users receiving it. Read Google's definition again, because the second half is the part that matters: "Users who already have these APKs are unaffected."
Halting is not a recall. Everyone who already installed the bad build still has the bad build. If the problem is data loss or a crash on launch, halting buys you time and nothing else. The actual fix is a new release with a higher versionCode rolled out over the top.
To halt through the API, PUT the track with the release status set to halted. In Play Console it is the "Halt rollout" action on the release.
You can halt a completed release as well as an inProgress one, which is the emergency case: a release that went to 100% and turned out to be broken.
Resuming means setting the status back to inProgress with a fraction, or to completed. Google does not offer an "undo halt to exactly where it was" operation, so note the percentage before you halt if you intend to resume at the same level.
What the API will not tell you
Two honest gaps, both of which change how you should build any dashboard on top of this.
Google exposes no release dates. The tracks endpoint returns the current release on each track with its status, name and version codes. There is no timestamp anywhere on it. If you need to order releases chronologically, versionCode is the only signal you have, and it is a proxy rather than a date.
Google exposes no release history. You get the current release per track, not the last ten. A release that has been superseded is simply gone from the API. If you want a history, you have to record it yourself as it happens.
That second point has a practical consequence: nobody can reconstruct your Play release history after the fact. Not you, not a tool, not Google support. Whatever you did not capture at the time is not recoverable.
Reading a status against a track
The same status means different things depending on where it sits.
completedoninternal: your testers have it, and nothing has been reviewed.completedonproduction: it is live to everyone who is eligible.inProgressonproductionat 100%: not a thing. At 100% it should becompleted.drafton any track: uploaded, assigned, and not sent. Usually because the edit was committed withchangesNotSentForReview=true, which Google requires in some situations such as an unpublished app or an update already in review.haltedonproduction: somebody pressed stop, and the users who already updated are still on it.
AppSubmit reads these four statuses straight off the tracks endpoint and shows them as Rolled out, Staged rollout, Draft and Halted, with the raw status kept alongside. The mapping is the table above, and you can read the same values yourself from edits.tracks.list if you would rather.