Google Play App Intel avatar

Google Play App Intel

Pricing

from $1.70 / 1,000 app record delivereds

Go to Apify Store
Google Play App Intel

Google Play App Intel

Get a public intel card for any Google Play app by package id or Store URL: rating, category, description, icon, last-update date, install-count band and content rating. No login, no Google API key.

Pricing

from $1.70 / 1,000 app record delivereds

Rating

0.0

(0)

Developer

Tim Zinin

Tim Zinin

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

4 days ago

Last modified

Share

Google Play App Intel: The Public App-Detail Card, Read Correctly, With No Reviews Promised

How this Actor turns your input into a decision This Actor reads exactly one thing: the public app-detail page Google Play shows anyone who opens play.google.com/store/apps/details?id=<package>. Give it a package id or a Store URL and you get back one JSON row — rating, category, description, icon, last-update date, an install-count band and a content rating — sourced from the same page, parsed twice: once from Google's own structured data, and once from the plain HTML fields Google keeps outside that structured data. No login, no Google API key, no headless browser, no anti-bot bypass.

The billable outcome is a delivered app: a package id that resolved to a live app-detail page and was parsed with confidence. A package id that Google Play itself confirms does not exist — a genuine HTTP 404, not a guess — is returned as a free row. A request that was blocked, timed out, or came back in a shape this Actor could not parse is also returned free, with a named reason. You never pay to learn that an app doesn't exist, and you never pay for a request that failed before it produced a usable result.

What you get

  • The rating straight from Google's own structured data, not a number scraped out of a star-icon count or a screen-reader label. Every card ships one <script type="application/ld+json"> block typed SoftwareApplication; this Actor parses that block by its declared @type, not by assuming it's the first script tag on the page — the same defensive rule the paired App Store Actor needs to enforce for real (see Evidence and boundaries), applied here too in case Google Play ever changes shape.
  • The four fields Google Play keeps OUTSIDE that structured data. updatedOn, downloadsText and contentRating are not JSON-LD properties — they sit in the page's plain HTML markup, in Google's own display text. A parser that only reads ld+json keys reports these fields as "missing" on a page where they are sitting in plain sight at HTTP 200. This Actor reads both sources on every request and tells you which one supplied which field.
  • A bonus field Google Play's structured data quietly includes: ratingCount, the number of ratings behind the score. It wasn't part of this Actor's original field list — it turned up live in the same ld+json block that carries ratingValue, on every app probed while this Actor was built, so it ships as a bonus, called out honestly as unconfirmed-stable rather than a documented Google API.
  • A field Google Play deliberately does NOT ship, on purpose: an exact "current version" string. See Limits for exactly why — the honest answer is that no single labeled version field exists on the current page, and picking the first version-shaped string out of an internal changelog blob would be a guess wearing a fact's clothes.
  • Flexible input. A bare package id (com.whatsapp) or a full Store URL — both normalize to the same validated request automatically, and a URL pointing anywhere other than play.google.com is rejected before any network request leaves this Actor (see the SSRF-guard example under Real happy, partial and failure output).
  • Honest not-found, never a guess. A nonexistent package id gets Google Play's own HTTP 404 — verified live, re-verified again while writing this page — and comes back as status: "not_found", at no charge. A blocked or malformed request is a different outcome, status: "error", so "this app doesn't exist" is never confused with "something went wrong on this request."
  • Built for watchlists. Up to 100 package ids per run, concurrency configurable 1–20, so tracking a dozen competitor apps for rating drift or a content-rating change is one scheduled run, not a dozen manual page loads.
  • Runs on Apify: trigger it from the Console, a scheduled Task, the Apify API, the Apify MCP server, or any HTTP client that can call an Actor run — export results as JSON, CSV, Excel, or push them straight into a pipeline via a webhook or integration.

Who uses it

  • Competitive and market intelligence teams who watch a specific set of competitor apps for rating movement, a content-rating change, or an install-band jump ("100M+" crossing to "500M+"), without opening the Play Store by hand for each one.
  • ASO and mobile marketing teams who need a machine-readable snapshot of the exact fields Google Play shows a shopper on the detail page — the fields that actually influence a store-listing decision — fed into a dashboard or a weekly report instead of screenshotted by hand.
  • Investor and analyst research building a lightweight, always-current app-portfolio table for a company under diligence — install-count band and last-update recency are both cheap, honest signals of whether a product is actively maintained.
  • Developers and agent/workflow builders who want a flat, three-state contract — found / not_found / error — that an agent or an n8n/Make node can branch on directly, without parsing Google Play's HTML or handling ld+json extraction themselves.
  • Buyers of the paired appstore-app-intel Actor who want the Android half of the same competitive set on the same schema and the same price, so one dashboard can show both stores' cards side by side without reconciling two different vendors' field names.

Where this Actor sits in your workflow

How to run

  1. Click Try for free — the free plan needs no card.
  2. Paste one or more package ids or full Store URLs into Package IDs or Store URLs, one per line. A bare package id (com.whatsapp) and a full URL (https://play.google.com/store/apps/details?id=com.whatsapp) are both accepted and normalized to the identical request; you don't need to pick one format for a whole list.
  3. Leave Max concurrent requests at its default of 5 unless you have a specific reason to raise it. Sustained load against Google Play has not been load-tested by this Actor's own build process — see Operating guide for what that means in practice.
  4. Press Start. Start it from the Console, an Apify Task on a schedule, the Apify API, or an MCP client — the run behaves identically from any of them.
  5. Read the Dataset for one row per app — in the Console's table view, pulled via the Apify API, or pushed onward by a connected webhook or integration.
  6. Check this run's key-value store record OUTPUT for a one-time run summary — requested/delivered/ paid/free/failed/incomplete counts, replay safety and any fatal error — written once at the end of the run.

There is no separate "review" or "confirm" step and no captcha to solve. A run either finishes with one row per requested app, or stops early only if this run's own charge budget runs out — see Pricing and Decision routing for exactly what a partial run looks like.

Pricing

Pay-per-event: $0.005 per run start, plus $0.002 per delivered app. A delivered app is a package id that resolved and was parsed as status: "found". A package id that comes back not_found (a confirmed 404) or error (a blocked or malformed request) is still written to the Dataset as a full row, with the reason — and is never charged.

Delivered app is the one pricing noun used everywhere on this page, in the Actor's pricing record, and in the Dataset's own terminology — a watchlist of 20 package ids where 17 resolve and 3 come back not_found is billed for 17 delivered apps plus one run start, not 20.

This price matches the paired appstore-app-intel Actor exactly, on purpose — a buyer running both Actors across a cross-platform competitive set gets one price to reason about, not two.

On Apify's own billing record these are metered as two separate platform events — apify-actor-start for the run fee, and result-found for each delivered app — the same two lines you will see on your invoice.

Input contract

  • appIds (required) — an array of strings, each either a bare Google Play package id ("com.whatsapp", reverse-DNS form) or a full Store URL ("https://play.google.com/store/apps/details?id=com.whatsapp"). Up to 100 entries per run; both formats can be mixed freely in the same list. A URL-shaped entry is checked against play.google.com as an exact parsed hostname (not a substring match — play.google.com.evil.example and evil.example/?host=play.google.com both fail this check) before anything is requested; a bare id containing /, .. or :// is rejected before it is substituted into a URL template. Duplicate entries are de-duplicated before billing, so the same package id listed twice is only ever charged once.
  • maxConcurrency (optional, default 5, range 120) — how many apps are fetched in parallel within one run. The default is deliberately conservative; see Operating guide.

Locale is not a buyer-controlled input. Every request this Actor makes carries &hl=en&gl=US, hard-coded — see Evidence and boundaries for exactly why leaving this to the runner's own IP address would silently break the English-token fields this Actor parses.

{
"appIds": ["com.whatsapp"]
}
{
"appIds": [
"com.spotify.music",
"https://play.google.com/store/apps/details?id=com.instagram.android",
"com.king.candycrushsaga",
"com.discord"
],
"maxConcurrency": 4
}

(Both are contract illustrations built from .actor/input_schema.json, not run output — an input has no result to reproduce. The second one is the exact input used for the four-app batch run whose real output appears throughout the next section.)

Real happy, partial and failure output

Every row shares one flat schema regardless of outcome: eight decision-envelope fields (appId, sourceUrl, status, evidence, confidence, partial, checkedAt, error) plus, on a found row, nine data fields. error is null on every honestly-completed row — including not_found — never a bare empty result with no explanation.

Every JSON block below is copied verbatim from this Actor's own local runs (apify run --purge), executed while writing this page. Nothing here is invented, and nothing is backfilled from documentation — where a shape genuinely was not observed live, that is stated plainly instead of guessed.

Happy path — com.whatsapp, both sources parsed clean

{
"appId": "com.whatsapp",
"sourceUrl": "https://play.google.com/store/apps/details?id=com.whatsapp&hl=en&gl=US",
"status": "found",
"evidence": "ld+json SoftwareApplication + html fallback (2/2)",
"confidence": "high",
"partial": false,
"checkedAt": "2026-08-17T20:23:05.144Z",
"error": null,
"title": "WhatsApp Messenger",
"ratingValue": 4.625335693359375,
"ratingCount": 241373160,
"category": "COMMUNICATION",
"description": "Simple. Reliable. Private.",
"iconUrl": "https://play-lh.googleusercontent.com/Gqxk4T0uZsDwFp07DE-508hkyvcNmgFuRwPiwTEfF7D7OzGv1FdHDzEyMxNsSBZLOJlGpe3ULvVM2RgrRAlBqA",
"updatedOn": "Aug 15, 2026",
"downloadsText": "10B+",
"contentRating": "Everyone"
}

Happy path, second example — com.spotify.music, different category, different install band

Run in the same batch as three other apps (Instagram, Candy Crush Saga, Discord — all four came back confidence: "high", partial: false, evidence 2/2), to check that the two HTML-fallback fields aren't tuned to one app's markup:

{
"appId": "com.spotify.music",
"sourceUrl": "https://play.google.com/store/apps/details?id=com.spotify.music&hl=en&gl=US",
"status": "found",
"evidence": "ld+json SoftwareApplication + html fallback (2/2)",
"confidence": "high",
"partial": false,
"checkedAt": "2026-08-17T20:23:33.192Z",
"error": null,
"title": "Spotify: Music and Podcasts",
"ratingValue": 4.342593193054199,
"ratingCount": 36138686,
"category": "MUSIC_AND_AUDIO",
"description": "Listen to songs, play podcasts, create playlists and discover music you'll love",
"iconUrl": "https://play-lh.googleusercontent.com/IzQgYCcnCFCD08GR-3bdtcT8xzOvrNkC84avGT5CwTX2VIqmTmKKJcP_Cd4JoBOdmCMlTndlOzV6hrthg2fOWA",
"updatedOn": "Aug 17, 2026",
"downloadsText": "1B+",
"contentRating": "Teen"
}

Twelve live apps were checked across this build in total — WhatsApp, Instagram, Spotify, Candy Crush Saga, Discord, Minecraft, Uber, Snapchat, Netflix, eBay, Google Translate and Duolingo, spanning Communication, Social, Music, Games, Maps, Entertainment, Shopping, Tools and Education categories. Every single one came back confidence: "high", partial: false, evidence 2/2 — the honest finding is that no live partial: true row was produced across this test set. That is reported plainly rather than invented: see the note under Field dictionary for exactly what would cause one, and the sibling appstore-app-intel Actor's README for a real partial: true row from the same shared parsing library against a different store, since that store's larger HTML-field set did produce one live.

Not-found path — a real, confirmed 404

{
"appId": "com.this.does.not.exist.zzz999",
"sourceUrl": "https://play.google.com/store/apps/details?id=com.this.does.not.exist.zzz999&hl=en&gl=US",
"status": "not_found",
"evidence": "http 404 (Google Play's own \"Not Found\" page)",
"confidence": "high",
"partial": false,
"checkedAt": "2026-08-17T20:23:04.507Z",
"error": null
}

This is Google Play's own answer, not an assumption this Actor makes about what a missing package "should" return. It is a genuine HTTP 404 with Google's own "Not Found" page as the body — not a redirect to search, and not an HTTP 200 with an empty or generic body, either of which would need very different handling. Confirmed by re-running this exact case live while writing this page.

Failure path — the SSRF guard, a real blocked request

{
"appId": null,
"sourceUrl": null,
"status": "error",
"evidence": "input validation failed before any request was made",
"confidence": "low",
"partial": false,
"checkedAt": "2026-08-17T20:24:21.108Z",
"error": "blocked host \"evil.example\" — only play.google.com URLs are accepted"
}

Input for this run was ["https://evil.example/store/apps/details?id=com.whatsapp", "com.whatsapp"]. The first entry is rejected before any request leaves this Actor — no DNS lookup, no connection attempt, nothing sent to evil.example — while the second, legitimate entry in the same run is processed and billed normally (ok=1 found (billed), 0 not found, 1 errored, of 2 total in this run's own log). A status: "error" row is always free — this one included.

This is one of two ways an error row can happen; the other, an anomalous HTTP 200 that does not carry the expected ld+json block, was not observed live against this store during this build (Google Play's card returned the expected block on every one of the twelve apps checked) — see Limits for what that path looks like in the code, honestly labeled as untested against a live anomaly rather than demonstrated.

Field dictionary

FieldMeaningImportant boundary
appIdThe package id you passed, or extracted from a Store URLnull on a row where input validation failed before an id could be extracted
sourceUrlThe exact URL requestedAlways carries &hl=en&gl=US — not something you set; see Evidence and boundaries
statusfound, not_found (confirmed 404) or error (blocked/malformed request)The billing boundary — only found is charged
evidenceWhat confirmed the resultOn a found row, states which of the two HTML fallback fields parsed, e.g. "(2/2)"; on not_found, states the 404 was Google's own page, not a guess
confidencehigh, medium or lowhigh when the ld+json block AND both HTML-fallback fields parsed; medium when only ld+json parsed; low on any error row
partialtrue if some expected HTML-fallback field was not found on this rowWas never observed true across twelve live apps tested for this page — see Real happy, partial and failure output
checkedAtISO timestamp of this row's own checkIndependent per row within a run, not shared across the whole run
errorHuman-readable reasonnull on every honestly-completed row, including not_found — a message only appears on status: "error"
titleThe app's display name, from ld+jsonnull only on a non-found row
ratingValue0–5 star rating, from ld+json aggregateRating.ratingValueA raw float as Google publishes it, not rounded
ratingCountNumber of ratings behind that scoreBonus field — found live in ld+json on every app checked, but not part of any documented stable Google API, so treat it as best-effort
categoryGoogle Play's own category constant (e.g. "COMMUNICATION", "GAME_CASUAL")The exact string Google's structured data publishes, uppercase with underscores
descriptionThe short tagline text from ld+jsonPublisher-supplied free text, stored as-is, never executed or interpreted
iconUrlDirect link to the app's Play Store icon imageFrom ld+json, not re-hosted
updatedOnDisplay text of the card's "Updated on" fieldFrom plain HTML, not ld+json — a display string ("Aug 15, 2026"), not a machine timestamp
downloadsTextDisplay text of the card's install-count bandFrom plain HTML — a band, never an exact count ("10B+", "1B+", "500M+") because Google Play itself never publishes an exact number on this page
contentRatingThe card's content-rating label (e.g. "Everyone", "Teen")From ld+json's contentRating property

Evidence and boundaries

Why reviews and keyword search are permanently out of scope, not a v2 promise. Google Play's own robots.txt, read live while writing this page, states plainly:

Disallow: /store/getreviews
Disallow: /store/search
Disallow: /store/purchase
Disallow: /store/xhr

Both the reviews endpoint and the search endpoint are explicitly disallowed. This Actor will never touch either path, on this account or any other — not because reviews and search aren't valuable (they are; review-focused competitors in this same category carry several times this Actor's own traffic), but because the source's own published policy forbids it. The paths this Actor actually reads — /store/apps/details, and nothing else — are not disallowed by any rule in that file, checked directly, line by line, not assumed.

Why locale is hard-coded, not a buyer setting. Every request carries &hl=en&gl=US, fixed in code, never taken from input. Without it, the page renders in whatever locale the runner's own IP address implies — and every field this Actor parses from plain HTML depends on matching an English-language label ("Updated on", "Downloads"). A request without the locale parameters would return HTTP 200 with a fully valid, well-formed page — just not in English — and the label-matching fallback fields would silently come back empty. That is a success-shaped failure: a 200 status code with data quietly missing, exactly the class of bug this whole product line is built to avoid. The locale parameters are therefore not configurable input; they are non-negotiable, per-request, and identical on every run regardless of where the Actor executes.

Why the ld+json block is selected by @type, not by document position — defensively, here. The sibling appstore-app-intel Actor in this same pair has a genuinely critical version of this problem: an Apple app-detail page carries two ld+json blocks, and the first one has no rating at all (see that Actor's own README for the live proof). Google Play's card, checked across all twelve apps probed during this build, carries exactly one SoftwareApplication block — so this specific trap has not been observed live on this store. This Actor still selects the block by its declared @type rather than assuming "the first script tag is always the right one," because the cost of that discipline is zero and the cost of being wrong if Google Play ever ships a second block (an Organization block, a breadcrumb block, a review-aggregate block — any of which are common patterns on other commerce sites) is a silently empty rating at HTTP 200. This is documented as a defensive design choice, not a live-observed vulnerability on this specific store.

The version field that this Actor deliberately does NOT ship. Google Play's current app-detail page carries no single field labeled "Version" or "Current Version" anywhere in its visible markup. The only version-shaped strings on the page live inside an unlabeled internal JavaScript data blob that carries many historical version-history entries, in non-chronological order — release-note rollout metadata, not a clean "this is the current version" field. Grabbing "the first X.Y.Z.W-shaped string" out of that blob is the exact same class of mistake as trusting the wrong ld+json block by position: a confident-looking value pulled from the wrong place. Rather than ship a field that would be right by luck on some apps and wrong on others, this Actor does not promise a version field in this build. (The App Store product in this same pair does ship version, sourced from a genuinely labeled, order-guaranteed "Version History" panel — a different page shape entirely; see that Actor's own README.)

The SSRF/DNS-rebinding guard, and what it actually checks. A URL-shaped input is validated against the exact parsed hostname (play.google.com — an exact match, never a substring, so play.google.com.evil.example fails this check the same way evil.example does). Beyond hostname validation, every outbound request resolves DNS itself, judges every returned address against the private and reserved ranges (loopback, link-local, RFC 1918, CGNAT, cloud-metadata 169.254.169.254, and their IPv6 equivalents), and then pins the connection to exactly the addresses just checked — closing the gap where a second DNS lookup, made later by a less careful client, could answer differently and route the real connection somewhere the first check never saw. This is the same guard used across every Actor in this vendor's fleet; the live example under Real happy, partial and failure output shows it rejecting a URL pointed at evil.example mid-run without ever touching the network.

The byte cap, and why a cut-off page is never parsed as if it were complete. Every response is capped at 3,000,000 bytes, streamed rather than buffered all at once — comfortably above the roughly 1.2MB this Actor's own pages have measured at live, with headroom for a future page-size increase. A response that hits the cap is flagged truncated and returned as status: "error", never silently parsed as a complete page — the closing structure of the very field being parsed could simply be the part that got cut off.

The retry policy, stated precisely. A request is retried (up to two additional attempts, with exponential backoff) only for network-level failures, HTTP 429, and HTTP 5xx — never for a 404, which is the source's own confirmed answer, not a hiccup, and never for a 200 response whose body simply didn't have the expected shape, which is an anomaly this Actor reports honestly rather than hammers the source retrying.

Decision routing

Row shapeBillable?statusconfidenceWhat it means
Full parseYesfoundhighld+json and both HTML-fallback fields all parsed
Partial parseYesfoundmediumld+json parsed; one or both HTML-fallback fields did not — partial: true
Confirmed missingNonot_foundhighGoogle Play's own HTTP 404 — a genuine confirmed negative, not a guess
Input rejectedNoerrorlowSSRF/format validation failed before any request left this Actor
Fetch failedNoerrorlowTimeout, DNS failure, non-404 HTTP error, or a byte-cap truncation
Anomalous 200NoerrorlowHTTP 200 but the expected ld+json block was not found — not observed live on this store during this build, but handled the same way as any other anomaly
Budget exhausted mid-runNoThe run's own charge cap was reached; remaining package ids in that run are neither processed nor charged, and this is reported as a free explanatory row

A workflow only needs two rules: act on status === "found", and treat partial === true as "usable, but check evidence for exactly which field is missing before trusting the row completely."

Commercial playbooks

Competitor rating and content-rating watch

A mobile marketing team lists five to ten direct competitor apps and schedules a daily run. Most days, every field is unchanged — the value is in catching the day ratingValue drops half a star after a bad release, or contentRating shifts from "Everyone" to "Teen" after a feature update, days before that shows up in a manual spot-check.

Portfolio maintenance-recency signal for diligence

An analyst building a lightweight competitive or acquisition-target table uses updatedOn as a cheap, honest proxy for whether a product is actively maintained — an app last updated eight months ago next to one updated last week tells a real story with two fields and zero manual research.

ASO snapshotting for a store-listing audit

An ASO consultant pulls title, description, category and contentRating for a client's own app plus every direct competitor, side by side, ahead of a listing-optimization pass — the exact fields a shopper sees on the detail page, not an internal analytics number no shopper ever encounters.

Cross-store competitive dashboard, paired with appstore-app-intel

A BD or product team tracking the same app portfolio on both stores runs this Actor alongside appstore-app-intel on the same schedule. Both share the same decision-envelope shape (status/confidence/evidence/partial) and the same price, so one dashboard query can union both Actors' Dataset output without reconciling two different vendors' field names or two different pricing models.

Integration recipes

Scheduled watchlist

An Apify Task runs this Actor daily or weekly against a fixed list of package ids, reading the Dataset for status: "found" rows and diffing ratingValue/updatedOn/downloadsText/contentRating against the previous run's values — this Actor itself is stateless per run, so change-detection is a job for the calling workflow, not a feature promised here.

Agent/MCP pattern

An agent supplies a package id or Store URL and reads status before trusting any other field — found means the rest of the row is real data, not_found means the app genuinely does not exist right now, and error means retry later or surface the error string to a human, never silently substitute a guessed value.

Data-warehouse append pattern

Dataset rows are appended keyed by appId (present on every row shape, even error rows where input validation still extracted a candidate id) plus checkedAt; null is preserved as null in every field rather than coerced to an empty string, so a downstream query can tell "Google Play did not publish this field" apart from "this field is genuinely blank text."

Operating guide

Why maxConcurrency defaults to 5, not 20. The measured Gate B check that proved this source reachable was a single request per app, on two independent network legs — not a sustained burst. Raising concurrency to the schema's own ceiling of 20 on a large watchlist has not been load-tested by this Actor's own build process; the conservative default keeps a normal run comfortably inside behavior that has actually been observed, and a buyer running a large watchlist (60–100 package ids) is better served raising concurrency gradually and watching the run log than assuming the ceiling is safe on day one.

Reading a medium-confidence row. confidence: "medium" means the ld+json structured data parsed cleanly (so ratingValue, title, category, description and iconUrl are all trustworthy) but at least one of the two HTML-fallback fields (updatedOn, downloadsText) did not. Check evidence for the exact count ("(1/2)" vs "(2/2)") and treat the specific null field as genuinely unknown, not zero or empty.

Batch sizing. Up to 100 package ids per run is a schema-enforced ceiling, also enforced independently in the Actor's own code as a defense against a caller bypassing the Console form and hitting the API directly with a longer list. A watchlist larger than 100 needs more than one run.

Why the pair (googleplay-app-intel + appstore-app-intel) shares one price and one envelope shape, on purpose. Both Actors were built together, from the same underlying library, specifically so a buyer tracking a cross-platform app portfolio does not have to reconcile two vendors' different field names, different pricing units, or different confidence semantics — status/confidence/evidence/partial mean exactly the same thing on both.

Related tools for adjacent workflows in e-commerce.

ActorWhat it does
App Store App IntelPair it in the e-commerce workflow: Get a public intel card for any iOS App Store app by numeric id, slug+id, or Store URL: rating, review...
Shopify Catalog & Merchandising IntelligencePair it in the e-commerce workflow: Qualify Shopify merchant leads with evidence-backed public catalog coverage, observed price positioning,...
Zid Product Export IntelligencePair it in the e-commerce workflow: Turn merchant-owned, merchant-authorized, or otherwise licensed Zid product exports into stable evidence,...
Shopify Store Price & Catalog Change MonitorPair it in the e-commerce workflow: Monitor store-level price range, catalog size, currency, Shopify detection, and heuristic revenue-band...
Chotot Vietnam Listings ScraperPair it in the e-commerce workflow: Normalize authorized or licensed classified listings into provenance-linked review rows with stable...

FAQ

Does this need a Google account or an API key?

No. It reads the same public detail page anyone sees in a browser, unauthenticated, the same way a person's own browser would load it.

Can I get reviews or review text?

No, and this is permanent, not a "not yet." Google Play's own robots.txt explicitly disallows /store/getreviews — this Actor will never touch that path, on this account or any other.

Can I search by keyword or category?

No, same reason. robots.txt explicitly disallows /store/search. This Actor only ever reads a specific app's detail page by id — it has no discovery function.

What exactly does downloadsText give me — an exact install count?

No. It is Google Play's own display band"10B+", "1B+", "500M+" — exactly as shown on the card. Google Play itself does not publish an exact install count on this page; no field on this Actor claims to have one.

Why is there no version field?

Because Google Play's current page carries no single labeled "current version" field — only an unlabeled internal data blob with many historical version strings in non-chronological order. Shipping a guessed value from that blob would be worse than shipping nothing; see Evidence and boundaries for the full reasoning.

What happens if I pass a package id that doesn't exist?

You get a status: "not_found" row, confirmed by Google Play's own HTTP 404 — verified live, not a guess — and you are not charged for it.

What happens if the request is blocked or malformed?

A status: "error" row, free, with a specific reason in the error field — a blocked host (SSRF guard), a DNS or timeout failure, or an HTTP 200 whose body didn't carry the expected structured-data block.

Can I call this from an AI agent?

Yes — it's a standard Apify Actor, callable from the Apify API, the Apify SDK, or the Apify MCP server, with a flat, three-state status field designed to be branched on directly.

How current is the data?

As current as Google Play's own live page at the moment this Actor's request lands — there is no caching layer in this Actor and no delay beyond one HTTP round trip.

Am I allowed to use this for a commercial competitive-intelligence product?

The data itself — rating, category, description, install band — is what Google Play already displays publicly to every visitor of the page; this Actor does not access anything gated behind a login. See Sources and rights for exactly what was checked and what was not.

How reviewed is this build before release?

This Actor's core parsing logic (ld+json block selection, HTML-fallback field extraction, the SSRF/DNS guard, and the billing guard) is shared, line for line, with the paired appstore-app-intel Actor — a defect found and fixed in one shows up fixed in both, and both Actors' acceptance.json goldens (5 cases each: positive parse, HTML-fallback presence, locale enforcement, confirmed-404 negative, and SSRF-guard rejection) were run against this exact build.

Sources and rights

Everything below is read from the public app store listing page of the app you name — the same HTML any visitor receives, fetched anonymously with no login and no API key.

The only source. The public Google Play app-detail page, https://play.google.com/store/apps/details?id=<package>&hl=en&gl=US. No other Google Play path, no Google Play Developer API, and no private or undocumented endpoint of any kind.

What was checked, stated precisely. play.google.com/robots.txt was read live while writing this page and confirms /store/apps/details — the only path this Actor requests — carries no Disallow rule. /store/getreviews, /store/search, /store/purchase and /store/xhr are all explicitly disallowed, and this Actor does not request any of them.

What was NOT checked, stated equally precisely. This Actor's build process verified robots.txt permission for the specific path it reads. It did not conduct a legal review of Google Play's Terms of Service regarding automated collection, storage, or resale of app-listing data, and makes no claim about what that document does or does not permit beyond the crawler-access question robots.txt answers. The app title, description, icon and category displayed on the card are the publisher's own marketing text and assets, already made public by the publisher on this same page — not confidential or gated data — but a buyer building a product on top of this Actor's output is responsible for their own review of how they intend to use it, the same way they would be building on any other public web page.

No warranty of continued availability. Google Play can change this page's markup, its structured-data shape, or its access policy at any time, without notice. This Actor's own acceptance tests are pinned to a live snapshot (17.08.2026) and are expected to need updating if the source's shape drifts — that is a disclosed, ordinary maintenance reality of reading a live commercial web page, not a guarantee this page makes about the future.

Limits

  • No reviews, no review text, no review count trend, and no keyword or category search — permanently, by robots.txt, not a roadmap item.
  • No exact install count — only Google Play's own display band ("10B+", "1B+", etc.).
  • No version field — Google Play's current page carries no single reliable "current version" field; see Evidence and boundaries for why one is not guessed.
  • Locale is fixed to hl=en&gl=US on every request — not a buyer-configurable input, and not tested in any other locale.
  • Sustained high-concurrency load has not been load-tested against this source by this Actor's own build process — Gate B verification was one request per app on two independent network legs, not a burst. maxConcurrency defaults conservatively for this reason.
  • ratingCount is a bonus field found live in structured data during this build, not a documented stable Google API — treat it as best-effort, not a guaranteed-present field forever.
  • HTML-fallback fields (updatedOn, downloadsText) are capped at 60 and 30 characters respectively and truncated with if Google Play's own display text is ever longer; this was not observed to trigger on any of the twelve apps checked during this build, but the cap exists and is disclosed.
  • partial: true was never observed live across the twelve apps checked while building and documenting this Actor — the code path exists (see Field dictionary and Decision routing) but has no live example on this specific store; the sibling appstore-app-intel Actor's README has a real one from the same shared library.
  • Up to 100 package ids per run; a larger watchlist needs more than one run.
  • This Actor reads one region's worth of data per app (Google's own default English/US rendering, forced via hl=en&gl=US) — it does not compare pricing, availability, or listing differences across regions.

Support boundary

Actor support covers this page's described input validation, the source-access and parsing behavior, the Dataset schema, and the billing/replay guarantees for an accepted run. It cannot guarantee Google Play's own uptime, page shape, or continued publication of any specific field — Google can change any of this without notice, and this Actor's acceptance tests will need updating if it does. It cannot provide legal advice on Google Play's Terms of Service, cannot restore access if Google blocks this Actor's traffic pattern going forward, and cannot add reviews or keyword search — both are permanently out of scope by the source's own robots.txt, not a missing feature. When reporting a problem, include the Actor run ID, the exact appIds input used, the affected row's status/evidence/error fields, and the approximate checkedAt time.


Built by zinin. Questions? Telegram @timzinin.