playstore-cli
An unofficial Google Play Store client for the command line.
Search apps, read reviews and top charts, check official signing certificates, and download APKs.
$ playstore-cli search youtube -n 2
1. YouTube — Google LLC
com.google.android.youtube · 21.32.4 · 10,000,000,000+ downloads · Free
2. LINE: Calls & Messages — LINE (LY Corporation)
jp.naver.line.android · 26.15.1 · 1,000,000,000+ downloads · Free
$ playstore-cli download com.google.android.youtube
YouTube 21.32.4 (1561190505) — 4 files, 137.3 MB → downloads/com.google.android.youtube-1561190505
[1/4] base.apk (60.9 MB) OK
[2/4] split_config.arm64_v8a.apk (73.3 MB) OK
[3/4] split_config.en.apk (0.3 MB) OK
[4/4] split_config.xxhdpi.apk (2.7 MB) OK
Install: adb install-multiple downloads/com.google.android.youtube-1561190505/*.apk
Contents
- Features
- Installation
- Quick start
- How it works
- Commands
- Storage
- Play API behavior confirmed against real responses
- Development
- Caveats
- References
- License
Features
- Sign in with your Google account. A browser window handles the sign-in, 2-step verification included, and the long-lived token is stored locally.
- Search, details and permissions. Find package names and see versions, install counts and the permissions Play reports for your device.
- Ratings and reviews. The rating summary with its per-star distribution, plus reviews with developer replies, sorted by relevance or date.
- Top charts. Top free, top paid, top grossing and trending, for any category.
- Official signing certificates. The SHA-1 and SHA-256 Play reports for an app, optionally checked against the signing block of the APK it actually serves, by fetching only the end of the file.
- APK downloads. Base and split APKs and OBBs, verified against Play's hashes, ready for
adb install-multiple. Older versions too, while Play still has them. - Your own device profiles. Generate a profile from any Android phone over adb, so Play serves the same apps and builds as it would to that phone.
Installation
Requires Python 3.10+. Install it as a command-line tool with uv or pipx:
uv tool install playstore-cli
# or
pipx install playstore-cli
From a clone of this repository:
uv tool install .
You also need:
- A Chrome-based browser (Chrome, Chromium, Edge, Brave), used by
login - A Google account. A separate account for research is recommended (see Caveats)
- adb, only to generate your own device profiles with
profile
Quick start
playstore-cli login # 1. Google sign-in → AAS token
playstore-cli checkin # 2. register a virtual device → gsfId
playstore-cli search youtube # find package names
playstore-cli top Finance # top chart of a category (top free)
playstore-cli details com.google.android.youtube # details and permissions
playstore-cli reviews com.google.android.youtube # ratings and reviews
playstore-cli cert com.google.android.youtube # official signing certificate hashes
playstore-cli download com.google.android.youtube # download the APKs
login and checkin are needed only once. A Play session is created whenever needed, and when the Play token expires (401) it is rebuilt from the saved AAS token and the request is retried.
How it works
flowchart LR
login["login<br/>browser sign-in"] -->|AAS token| checkin["checkin<br/>register a virtual device"]
profile["device profile<br/>bundled or from adb"] --> checkin
checkin -->|gsfId| session["Play session<br/>Play token, device config, terms"]
session --> read["search · details · reviews<br/>top · categories"]
session --> fetch["cert · download"]
logingets a one-timeoauth_tokenfrom Google's "Add account" page and exchanges it for an AAS token atandroid.clients.google.com/auth.checkinregisters a virtual device, described by a device profile, and gets its gsfId.- A Play session (Play token, uploaded device configuration, accepted terms of service) is built from those two, then every command calls the Play Store's internal API (
android.clients.google.com/fdfe/…) as that device.
Commands
| Command | What it does |
|---|---|
login |
Sign in to a Google account → save an AAS token |
checkin |
Register a virtual device → save its gsfId |
profile |
Generate a device profile from an Android device or emulator over adb |
session |
Create a Play session (normally created automatically) |
search <query> |
Search apps |
top [category] |
Top charts by category (top free/paid, top grossing, trending) |
categories |
Category IDs and names |
details <package> |
App details and permissions |
reviews <package> |
Rating summary and reviews |
cert <package> |
SHA-1 and SHA-256 of the official signing certificates |
download <package> |
Download the base and split APKs and OBBs |
login
playstore-cli login [--email EMAIL] [--oauth-token TOKEN] [--timeout 600]
- Opens the Google "Add account" page (
accounts.google.com/EmbeddedSetup) in a browser window. Once you have signed in (including 2-step verification), it reads theoauth_tokencookie and exchanges it for a long-lived AAS token. The window closes by itself. - The browser profile used for the login is kept, so 2-step verification is usually asked only the first time.
- With
--oauth-token, no browser is opened. That token expires within minutes, so use it right away.
checkin
playstore-cli checkin [--profile NAME_OR_PATH] [--locale en_US] [--timezone UTC] [--force]
- Registers a virtual device with Google and gets its gsfId. All later Play requests are made as this device.
--profileis the device it pretends to be: a profile name (your own profiles in~/.config/playstore-cli/profiles/come first, then the bundled ones) or a path to a.propertiesfile. The default,galaxy_s10e, is a Samsung Galaxy S10e (SM-G970N) on Android 12, generated with profile and without operator codes. Play serves apps and builds that fit the profile's Android version, so make a profile of a newer phone for apps that need a newer Android.- Play answers in the device locale (app titles, install counts, category names). Prices are in the currency of the account's country.
- Every checkin adds a new device to the account, so an already registered device is reused. Pass
--forceto register a new one.
profile
playstore-cli profile [-s SERIAL] [--name NAME] [-o FILE]
Reading the device over adb...
Google sdk_gphone64_arm64, Android 15 (SDK 35)
google/sdk_gphone64_arm64/emu64a:15/AE3A.240806.043/12960925:userdebug/dev-keys
Saved: ~/.config/playstore-cli/profiles/emu64a.properties
Use it: playstore-cli checkin --profile emu64a --force
- Reads a device profile from an Android phone or emulator connected over adb (USB debugging on): build properties (
getprop), the screen, input, features, shared libraries and GL extensions (am get-config --device, the same data the Play Store uploads), the Google Play services and Play Store versions, and the system locales (from the resources offramework-res.apk). - Saves it as
~/.config/playstore-cli/profiles/<Build.DEVICE>.properties, ready forcheckin --profile. - The device decides which APK variants Play serves. Emulator images without Google Play are test-key builds (
userdebug/dev-keys), and Play gives them differently signed variants of some apps, Google's own in particular (see cert). Use a real phone, or an emulator image with Google Play, to get the variants regular phones get. - If the device has no real Play Store (emulator images without Google Play ship a stub), a current Play Store version is filled in, because Play rejects very old ones.
search
playstore-cli search youtube [-n 20]
1. YouTube — Google LLC
com.google.android.youtube · 21.32.4 · 10,000,000,000+ downloads · Free
2. LINE: Calls & Messages — LINE (LY Corporation)
jp.naver.line.android · 26.15.1 · 1,000,000,000+ downloads · Free
- Pages through the results and collects up to
-napps without duplicates, until Play stops returning more ("youtube" ends at 516). - Play returns popular apps even for a query with no match.
top
playstore-cli top [category] [-c free|paid|grossing|trending] [-n 20]
$ playstore-cli top GAME -c paid -n 3
GAME Top paid
1. Dungeon Merge! — Brilbox
com.brilbox.dungeonmerge · 1.0.41 · ₩70
2. Minecraft: Dream it, Build it! — Mojang
com.mojang.minecraftpe · 1.26.52.3 · ₩9,900
3. Stardew Valley — ConcernedApe
com.chucklefish.stardewvalley · 1.6.15.3 · ₩5,900
- The category is an ID (
FINANCE,GAME_PUZZLE; any case if it has an underscore) or a name ascategoriesprints it (Finance,Puzzle,Health & Fitness; any case). Without one, the chart covers all apps (APPLICATION). Names shared by several categories (Sports=SPORTS,GAME_SPORTS) need the ID. -c:freetop free (default),paidtop paid,grossingtop grossing,trendingtrending.- Ranks come 7 at a time and are collected up to
-n. Play goes to about rank 200 (Finance top free ends at 197, in about 5 seconds). Charts with few apps are shorter (top paid in Finance has 1). - Top chart responses carry no install count, so it is left out of the output.
categories
playstore-cli categories
APPLICATION Apps
ANDROID_AUTO_PROJECTED Android Auto
...
FINANCE Finance
...
GAME Games
GAME_ACTION Action
...
- The category IDs and names to pass to
top. Names come in the device locale (35 app and 17 game categories).
details
playstore-cli details com.google.android.youtube
Title YouTube
Package com.google.android.youtube
Version 21.32.4 (1561190505)
Developer Google LLC
Updated Aug 10, 2026
Installs 10,000,000,000+ downloads
Size 137.3 MB
Permissions 51
android.permission.ACCESS_ADSERVICES_AD_ID
...
The permission list is not the manifest as-is but a list Play adapts to this device: permissions whose maxSdkVersion is below the device SDK are dropped, and implied permissions are added. YouTube 21.40.161 for an Android 15 profile, for example, has 50 in its manifest, plus READ_EXTERNAL_STORAGE, implied by WRITE_EXTERNAL_STORAGE, for 51.
reviews
playstore-cli reviews com.google.android.youtube [-n 20] [-s relevant|newest] [-r 1-5]
YouTube — rated 2.9 (160,325,046 ratings)
★5 37% 59,074,625
★4 7% 11,500,147
★3 7% 11,071,871
★2 6% 9,385,508
★1 43% 69,292,500
20 reviews (most relevant)
1. ★★★★☆ <author> · 2026-07-07 · 21.25.529 · 1,102 found helpful
<review text>
└ Developer reply (<date>)
<reply text>
- The rating summary (average and per-star distribution) is followed by the reviews. Each review shows the author, date, app version at the time, "found helpful" count and the developer reply.
-s:relevant(most relevant, default) is the same order as the reviews on the Play Store web page;newestis newest first.-r: only reviews with that many stars.- Pages through the reviews up to
-n(100 per request).
cert
playstore-cli cert com.google.android.youtube [--apk]
cert |
cert --apk |
|
|---|---|---|
| Source | certificateSet in the Play details response |
The real APK's signing block (v2/v3/v3.1) |
| Requests | 1 details request | details, delivery (purchase if needed), a few Range requests for the end of the APK (YouTube: 3 requests, 80 KB) |
| Paid apps | Yes (no purchase) | No |
| Side effects | None | A free app not yet in the account library gets added to it |
-
certis enough when you only need the signing hashes.--apkchecks the hashes Play reports against the file it actually distributes. -
The hashes are hashes of the certificate (DER), the same as the certificate digest from Android's
PackageManagerorapksigner. -
The details list covers every APK variant Play serves, so it can hold several certificates, and the APK a device gets is signed by only some of them. YouTube (21.40.161 and 21.32.4):
Source Certificate SHA-1 details certificateSet4ba713df…,24bb24c0…APK for a release-keys device: v3 signer (Android 7-12, SDK 24-32) 24bb24c0…the same APK: v3.1 signer (Android 13+, SDK 33+) f8456b1d…(key rotation:24bb24c0…→f8456b1d…)APK for a test-keys device ( userdebug/dev-keysemulator image): v3 signer4ba713df…(the AOSP test key,CN=Android, O=Android)the same APK: v3.1 signer b971642d…Devices on Android 13 or later use the rotated keys, which are not in the details list. To compare with what a device reports, check the
--apkresult as well, made with a profile of the same kind of device. -
--apkdoes not support APKs signed with v1 (JAR) only.
download
playstore-cli download com.google.android.youtube [-v VERSION_CODE] [-o downloads]
YouTube 21.32.4 (1561190505) — 4 files, 137.3 MB → downloads/com.google.android.youtube-1561190505
[1/4] base.apk (60.9 MB) OK
[2/4] split_config.arm64_v8a.apk (73.3 MB) OK
[3/4] split_config.en.apk (0.3 MB) OK
[4/4] split_config.xxhdpi.apk (2.7 MB) OK
Install: adb install-multiple downloads/com.google.android.youtube-1561190505/*.apk
- Downloads the base APK, every split APK and the OBBs into
downloads/<package>-<versionCode>/. - File names follow the on-device
/data/applayout (base.apk,split_<name>.apk), so the printedadb install-multiplecommand installs them directly. OBBs ([main|patch].<versionCode>.<package>.obb) go in/sdcard/Android/obb/<package>/on the device. - Each file is checked against the size and SHA-256 (SHA-1 for OBBs) that Play reports. Incomplete or mismatching files are not kept, and files already downloaded and verified are not downloaded again.
-vdownloads an older version, if Play still has it. You need to know the version code. For a version Play doesn't have, it fails withno download URL for versionCode N.- Free apps only. An app not yet in the account library is added to it (purchase).
Storage
| Path | Contents |
|---|---|
~/.config/playstore-cli/account.json |
Email and AAS token (mode 600) |
~/.config/playstore-cli/device.json |
Device profile, gsfId, checkin tokens |
~/.config/playstore-cli/session.json |
Play token, deviceConfigToken, dfeCookie |
~/.config/playstore-cli/chrome-profile/ |
Browser profile for the login |
~/.config/playstore-cli/profiles/ |
Your own device profiles (from playstore-cli profile) |
downloads/ |
Downloaded APKs (git-ignored) |
Set the PLAYSTORE_HOME environment variable to use another directory than ~/.config/playstore-cli.
Play API behavior confirmed against real responses
Things that are missing from, or differ from, the reference implementations (gpapi, Aurora):
- Signing hashes: field 22 of
AppDetailsin the details response (certificateSet) holds each certificate's SHA-1 and SHA-256, base64url-encoded without padding. Paid apps and apps incompatible with the device have it too. It lists the signers of every variant Play serves (release-keys and test-keys devices get differently signed APKs of some apps), not only those of the APK the device gets (see cert). - delivery hashes: the file
sha1/sha256in a delivery response are base64url without padding too. - Missing versions: asking delivery for a versionCode Play doesn't have returns no error, only an empty
downloadUrl. - Search User-Agent: sending
searchwith the current Play User-Agent returns empty result clusters, so search uses a legacy User-Agent. Results continue along two paths: a cluster's next results (getCluster?…) and the next clusters (searchList?…). - Reviews:
revtakessort2for most relevant and0for newest. An unknown app returns an empty list (HTTP 200) instead of an error, and the next-page path (ptkn) carries the sort and star filter over. The "found helpful" count of a review is field 38, which gpapi doesn't define. - Top charts:
listTopChartItems?c=3&scat=<category ID>&stcid=<chart>, wherestcidisapps_topselling_free,apps_topselling_paid,apps_topgrossingorapps_movers_shakers. Ascatthat is not a category ID (such asapps) returns an empty list. The next 7 ranks continue through the cluster'sgetCluster?…. With the legacy User-Agent the same apps come back but some ranks are reordered (ranks 23-37 of top free in Finance, for example), so top charts use the device profile's User-Agent. - Category list: sending
categoriesListwith the current Play User-Agent returns a newer stream format withoutlistResponse, so it uses the legacy User-Agent like search. The category ID is in each document's category home path (homeV2?cat=FINANCE&c=3). - Permissions: the list is adapted to the device SDK rather than copied from the manifest: some permissions are dropped because of
maxSdkVersion, and implied permissions are added. - Download CDN: Google's CDN (
*.gvt1.com) rejects suffix ranges (bytes=-N) with 416. Both start and end have to be given. - Auth TLS:
/authreturns 403BadAuthenticationif TLS ALPN is sent or session tickets are disabled (the same tweaks as gpsoauth are needed).
Development
uv sync # create .venv with the project and the dev dependencies
uv run playstore-cli --help # run the CLI from the working tree
uv run pytest
uv build # build the sdist and wheel into dist/
After changing the proto, regenerate it from the repository root (protoc 28.x):
protoc -I. --python_out=. playstore_cli/googleplay.proto
Layout
playstore_cli/
__main__.py CLI
browser.py login step 1: get the oauth_token cookie through Chrome (DevTools protocol)
auth.py android.clients.google.com/auth: oauth_token → AAS → ac2dm / Play tokens
device.py device profile (.properties) → User-Agent, checkin request, device configuration
profile_dump.py device profile from a device over adb (getprop, am get-config, framework-res.apk)
checkin.py virtual device registration → gsfId
session.py Play token → device config upload → terms of service (toc)
api.py fdfe API: details, search, top_chart, categories, reviews, purchase, delivery
download.py delivery response → file list, download and verification
apksig.py APK signing block (v2/v3/v3.1) parser (local file / HTTP Range)
store.py account, device and session storage
googleplay.proto Play protocol definitions, only the messages needed (googleplay_pb2.py is generated)
profiles/ device profiles
tests/ tests against local HTTP stand-ins (no requests to Google)
Issues and pull requests are welcome.
Caveats
- This project is for research and learning. It uses an unofficial Google Play API, which may go against the terms of service, and the account may get restricted. Use a separate account for research.
- The AAS token is a long-lived token roughly as sensitive as the account password. Keep
account.jsonprivate. - Downloaded APKs are their developers' work. Don't redistribute them.
References
- Aurora Store / gplayapi: request headers, search, device profiles
- googleplay-api (gpapi): proto field numbers, checkin, delivery
- gpsoauth:
/authTLS settings - APK Signature Scheme v2
License
This project is not affiliated with or endorsed by Google. Google Play and Android are trademarks of Google LLC.
Metadata
Release files for playstore-cli 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| playstore_cli-0.1.0.tar.gz | 62.2 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| playstore_cli-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 118.2 kB
Release files / playstore_cli-0.1.0.tar.gz
| Download URL | playstore_cli-0.1.0.tar.gz |
|---|---|
| Size | 62.2 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
8e18debf83639a51b2aec31210d85fdd25c205645c6829bf96eb6e9458dbb1d6
|
|
BLAKE2b-256 checksum How to use checksums |
be5bb6e9aa45cfe812b68d4daa1174e998f85721a6fe10dbbd2d035ade5efb67
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / playstore_cli-0.1.0-py3-none-any.whl
| Download URL | playstore_cli-0.1.0-py3-none-any.whl |
|---|---|
| Size | 56.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
599f1f07f14c10db24ae10941c0da91ea14a1ae3d8efd199d41ad09c13a393df
|
|
BLAKE2b-256 checksum How to use checksums |
6179d7b95798a4d5f42e90d7c56ec66b406a6fd2d6c6a8e3bfb5ae1ce2e5829e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.11.19 {"installer":{"name":"uv","version":"0.11.19","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|