download-apk
Search Google Play and download an app's APKs into versioned folders, from the command line.
download-apk search "firefox"
download-apk download org.mozilla.firefox
Downloads land in apps/<appId>/<version>/: the base APK, its split APKs, and a metadata.json with
each file's SHA-256. Re-running a download picks up new releases and skips versions you already have.
It uses apkeep from the EFF to talk to Google Play, and google-play-scraper for search.
Requirements
- Python 3.10 or later.
- Google Chrome, for the one-time sign-in.
- A Google account. Use a spare one: Google may flag accounts that download apps outside a phone.
- Windows (x64) or Linux (x64 or arm64). apkeep publishes no macOS build.
Install
pip install download-apk
This adds a download-apk command. To keep it out of your other Python environments, install it
as a tool instead:
pipx install download-apk
uv tool install download-apk
To upgrade: pip install --upgrade download-apk, pipx upgrade download-apk or
uv tool upgrade download-apk. To run it once without installing: uvx download-apk search firefox.
Other ways in:
- From GitHub's newest release, without PyPI:
pip install https://github.com/jeremy-morren/download-apk/releases/latest/download/download-apk.tar.gz. - A specific release:
pip install download-apk==0.1.0, or the wheel attached to that release on GitHub. - The newest commit on
main:pip install git+https://github.com/jeremy-morren/download-apk.
Use
Search
download-apk search firefox --limit 3
App ID Title Developer Installs Rating Price
------------------------ ------------------------------ --------- ----------- ------ -----
org.mozilla.focus Firefox Focus: No Fuss Browser Mozilla 10,000,000+ 4.7 Free
org.mozilla.firefox_beta Firefox Beta for Testers Mozilla 10,000,000+ 4.6 Free
org.mozilla.fenix Firefox Nightly for Developers Mozilla 1,000,000+ 4.5 Free
--limit sets the number of results (default 20).
Download
download-apk download com.example.app
Downloading com.example.app from Google Play...
Saved com.example.app 1.4.2 to /home/me/apks/apps/com.example.app/1.4.2
-o/--out changes the output folder for one run. To change it for every run, set the
DOWNLOAD_APK_DIR environment variable; --out still wins over it. Without either, apps go to
./apps. Google Play only serves an app's current
version, so older versions can't be downloaded. Paid apps download only if the account owns them.
Options for every command
| Option | Default | What it does |
|---|---|---|
--ini PATH |
./.google-play.ini |
Where the Google Play credentials are kept |
--sign-in |
Sign in again, replacing the saved credentials | |
--email ADDRESS |
asked for | The Google account's email, used when signing in |
--country CODE |
detected | The store country for search and version lookups |
--lang CODE |
en |
The store language |
Without --country, the country is where your IP address is, which is also where Google Play places
you. It's looked up from Cloudflare's trace endpoint (https://www.cloudflare.com/cdn-cgi/trace), or
from https://ipinfo.io/country if Cloudflare doesn't answer, so those services see your IP address.
Offline, it falls back to your system's region setting, then to us. Every run logs which it used:
Determined Google Play country 'vc' from your IP address; use --country to override.
Signing in
Every command first makes sure you're signed in to Google Play:
- With no saved credentials, Chrome opens on Google's sign-in page. Sign in; the window closes by itself. Then type the account's email when asked.
- The one-time sign-in cookie is exchanged for a long-lived token, saved in
.google-play.iniin the current folder (or wherever--inipoints). - On later runs the token is checked first. If Google no longer accepts it, the browser sign-in runs again.
Downloading accepts the Google Play terms of service for the account.
Keep the credentials out of git
.google-play.ini holds a token that works like a password for the Google account. On every run,
download-apk checks whether that file is in a git repository and warns you:
- "credentials are already in git": the file is tracked. Remove it from the index with
git rm --cached .google-play.ini, add it to.gitignore, and sign in again with--sign-inso the token in the repository's history stops being the current one. - "credentials will be added to git": the file isn't ignored, so the next
git add .would commit it. Add.google-play.inito.gitignore.
Outside a git repository, or without git installed, nothing is checked.
Where files go
| What | Where |
|---|---|
| Downloaded apps | ./apps/<appId>/<version>/, or --out, or $DOWNLOAD_APK_DIR |
| Google Play credentials | ./.google-play.ini, or --ini |
| The apkeep program | Your user cache folder, e.g. %LOCALAPPDATA%\download-apk\Cache\tools on Windows or ~/.cache/download-apk/tools on Linux, downloaded on first use |
Develop
git clone https://github.com/jeremy-morren/download-apk
cd download-apk
python -m venv .venv
.venv/Scripts/activate # Windows; on Linux: source .venv/bin/activate
pip install -e . --group test # pip 25.1 or later
pytest
The tests run offline: Google Play, sign-in and apkeep are replaced with fakes, and the APKs are
generated. pytest -m network runs the few checks that talk to the real Google Play.
You can also run the command without installing it: python -m download_apk ... from the src
folder.
Releasing
GitHub Actions tests every push and pull request on Windows and Linux, with the oldest and newest
supported Python, and builds the package on each of those four machines. The builds must be
byte-identical. Then, for a push to main:
- Raise
versioninpyproject.tomland__version__insrc/download_apk/__init__.py; a test fails if they differ. - Push to
main.ci.ymlcreates a draft releasev<version>with two files: the wheel, and the source tarball asdownload-apk.tar.gz. If that release already exists it does nothing; if only a tag is left from a deleted release, it deletes the tag first. - Review the draft on GitHub and publish it. That creates the tag, and
release.ymlrebuilds the wheel from the tag, checks it is byte-identical to the release's wheel, and uploads it to PyPI.
PyPI accepts the upload through trusted publishing: its publisher settings for download-apk name
this repository, the workflow release.yml and the GitHub environment pypi, so no PyPI token is
stored anywhere. A version can be uploaded to PyPI only once, even if it is later deleted there.
Reproducible builds
Building the same commit on Linux gives byte-identical files, so anyone can check a release against its source:
git clone https://github.com/jeremy-morren/download-apk && cd download-apk && git checkout v0.1.0
python -m build
sha256sum dist/* # compare with the SHA-256 values in the release notes
hatch_build.pydates every file with the commit's time (throughSOURCE_DATE_EPOCH, which wins if you set it yourself) and reads the repository links from theoriginremote. The source tarball records both in.git-info.json, so a wheel built from the tarball, aspython -m builddoes, matches one built from a checkout.- hatchling, the build backend, writes files in a fixed order with fixed permissions.
pyproject.tomlpins it to one version, since another version may lay out the files differently. .gitattributeskeeps every text file LF in every checkout; a single CRLF file changes the wheel.
Windows builds hold the same files with the same contents, but not the same bytes: Python's zip
writer records the building OS in each entry, and Python 3.14 on Windows compresses with zlib-ng
rather than zlib. On Windows, compare contents instead: the RECORD file inside the wheel lists every
file's SHA-256, and it's identical on every OS.
Troubleshooting
- "Google Play has no app …, or doesn't offer it to this account." Check the app ID with
search. Some apps are limited to certain countries or devices. - "Google Play refused the saved sign-in." Run again with
--sign-in. - Chrome doesn't open. Install Google Chrome. Sign-in drives your installed Chrome through patchright; no separate browser download is needed.
License
MIT.
Metadata
Release files for download-apk 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| download_apk-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for download_apk-0.1.0-py3-none-any.whl |
Release files / download_apk-0.1.0-py3-none-any.whl
| Download URL | download_apk-0.1.0-py3-none-any.whl |
|---|---|
| Size | 16.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
12ee030fdbf5c2cfef92678148cb761f131a3ec8a506b68cb201ba15ea677998
|
|
BLAKE2b-256 checksum How to use checksums |
0bdb0eb4f91b5098f5433daa57eccc4dc3dfe9803389d70c9beef7ee67d96aaf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logSecurity for download-apk 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
1 file in this release has been uploaded with provenance metadata.
Attestation for download_apk-0.1.0-py3-none-any.whl
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 26, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.