Google Play App Check — Android App Domain Enrichment
Pricing
from $0.70 / 1,000 domain checked, no app founds
Google Play App Check — Android App Domain Enrichment
Company domain enrichment: does this company have an Android app on the Google Play Store? Matched on the developer's own listed website, not a name guess. Domains in, one row out: has_app, developer, top app, exact installs, rating, last updated. JSON/CSV/API/MCP.
Pricing
from $0.70 / 1,000 domain checked, no app founds
Rating
0.0
(0)
Developer
Brenton Keller
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
22 minutes ago
Last modified
Categories
Share
Google Play App Check — domain to app enrichment
Answers one question per row: does this company ship an Android app?
Company domains in, one row out. Not an app scraper — you do not need to know the app,
the developer or the package name. You give it basecamp.com and it returns 37signals,
com.basecamp.bc3, 1,432,472 installs and the date they last shipped.
Output: has_app, the developer and their listed website, and the developer's top app
with exact install count, rating, category and last-updated date.
Why this is hard, and why the match holds
Every other way of answering this breaks on the same thing: telling this company's
developer account from a same-named one. Play search never returns zero, so a name match
always finds something — allbirds.com returns 30 bird-watching apps.
This Actor matches on the developer's own website as published by Google Play, so the answer is a deterministic join rather than a guess.
Where this fits
| Cost | Answers "does this domain have an app?" | |
|---|---|---|
| This Actor | ~$0.002–0.004 per domain | Yes, deterministically |
| Clearbit · Apollo · ZoomInfo | enrichment plans | No — no app-store field at all |
| BuiltWith · Store Leads | $295–995/mo | Only a smart banner on the website; never inside the store |
| Apptopia · Sensor Tower | $12k–150k/year, annual contract | Yes — and priced for market research, not per-lead enrichment |
App-store intelligence has been enterprise-contract software. This is the same question
answered per domain, per credit, with no contract. Drops straight into Clay, n8n, Make or a
curl — see Use it from Clay below.
Read this before using
has_app: false
has_app: falsemeans "no app found", not "this company has no Android app."A
trueis strong evidence: it is a deterministic match against the developer's own listed website. Afalseis weaker — it means nothing in the search results claimed the domain, which is usually right but is still absence of evidence rather than evidence of absence.For scoring a prospect list,
trueis safe to act on; afalseshould widen a funnel, not disqualify a lead on its own. Check thehintcolumn: when a row's search was not exhausted, or the per-domain request ceiling was hit, the hint says so and thefalseis provisional.
Why the match is trustworthy
Every Play listing carries the developer's own website, so the match is a deterministic join on that website, not a name guess.
basecamp.com → 37signals is the case that proves it. The developer name shares no word
with the domain, so no amount of name matching finds it, and the website join resolves it
exactly. Name similarity is used only to decide which listings to check first — it never
decides the answer.
Modes
app-check is the product. app-search is a utility mode kept for the occasional
"what's on Play for this query" lookup; if you want a general Play scraper there are better
and cheaper ones on the Store. What is hard, and what this exists for, is the domain join.
app-check — enrichment, one row per domain
| Field | Example |
|---|---|
has_app | true |
developer_name | 37signals |
developer_website | https://basecamp.com |
developer_website_matches_domain | true |
matched_on | website or support_email |
top_app_title | Basecamp - Project Management |
top_app_installs | 1,000,000+ |
top_app_installs_exact | 1432472 |
top_app_rating_in_region | 4.636 |
top_app_category | Productivity |
last_updated / released | 2026-09-29 / 2015-11-03 |
days_since_update / app_maintenance_status | 8 / active — see below |
developer_app_count, top_app_package, support_email | |
region | us |
hint | set when an answer is provisional — read it |
Release recency is the filter worth building on, because it separates leads that look
identical on install count. app_maintenance_status is active (updated within 90 days,
so there is a mobile team to sell to), stale (90–365), or abandoned (over a year — a
poor SDK lead and a strong modernisation-agency lead). days_since_update ships alongside
it, so you can re-bucket on your own thresholds without re-running anything.
app-search — utility mode
Input search queries, get the full Play listing per result: package, title, developer, bucketed installs, rating, category. Useful for a one-off lookup; it is not what this Actor is for.
Input
{"domains": ["notion.so", "basecamp.com", "stripe.com", "allbirds.com"],"mode": "app-check","country": "us"}
Pass domains, not brand names or IP addresses. A brand has no website to join against,
so brand input returns has_app: null with a hint telling you to pass the domain — and is
not charged — rather than a guess that would be wrong most of the time. An IP address gets
a free error row saying so.
Cost and runtime
1. search Play for the domain 1 request2. order candidates by developer-name similarity free3. fetch listings until one claims the domain 1 typical4. fetch the developer's catalogue 1 request5. fetch their highest-install app 1 request
Measured over 15 graded domains and a 500-domain run, zero rate limits. A match costs
3–5 requests; a no-match costs ~11, because there is no early exit; the worst case is
capped by maxRequestsPerDomain (25 by default). An exact first-party match is the cheap
case — Play puts it in the hero card, and the hero is always the first candidate checked.
How many domains per run. At the no-match pace, the default 3,600 s timeout is reached
at roughly 218 domains, and about 215 finish because the run stops starting new
domains a minute early. Split larger lists into batches of ~200, or raise the run
timeout. The run's status message carries a measured ETA, and warns up front when the list is
longer than the timeout allows. Rows are saved as each domain finishes, and the run stops
cleanly a minute before its timeout: every domain it did not reach still gets a row, with
error starting not checked: and no charge, so you can filter those and resubmit them.
Proxy. None by default. On Apify's own network Google Play answered 10 of 10 test
domains with 0 rate limits and the same rows as a run from a separate network, so a proxy
adds nothing but latency. If rows come back with a blocked: error, enable Apify Proxy
with the RESIDENTIAL group; useApifyProxy: true with no group resolves to datacenter,
which Google blocks faster.
Use it from Clay
Clay has no native app-store column, so add one with Add enrichment → HTTP API (Growth plan or higher):
- Method
POST - URL
https://api.apify.com/v2/acts/brenton8907~google-play-app-check/run-sync-get-dataset-items - Query parameters
token= your Apify API token (store it as a Clay secret);maxTotalChargeUsd=0.02as a hard per-row spend ceiling; optionallyfields=query,has_app,developer_name,developer_website,top_app_title,top_app_installs_exact,top_app_rating_in_region,last_updated,days_since_update,app_maintenance_status,hintto keep Clay's field picker short. - Header
Content-Type: application/json - Body
{"domains": ["{{domain}}"], "mode": "app-check", "country": "us"}— replace{{domain}}with your domain column. A bare domain,www.prefix or full URL all work.
The response is a one-element array; map has_app, top_app_installs_exact and the rest from
item 0. A cold call takes ~10–35 s, so raise Clay's HTTP timeout if it cuts off. For a list of
hundreds, one batch run with every domain in domains is faster than a call per row: join the
output back on query, which echoes your input verbatim, and resubmit any row whose error
starts with not checked:.
Four things that would otherwise give you the wrong app
The app that matches is usually not the company's main app. Notion's listing for
Notion Calendar (3.6M installs) matches before the 43.9M main app; searching figma.com
reaches Figma for Government (338 installs) first. Reporting those as "the company's app"
would badly understate a company's footprint, so once the website identifies the
developer, the actor reads their catalogue and reports their highest-install app.
One developer can publish under several websites. Stripe's Dashboard app lists
stripe.com while their Link app lists link.com, and only Link appears when searching
stripe.com. A failed website match therefore does not clear a developer: if their name
looks right, their other apps are checked too. Without this, stripe.com answered "no".
Some listings name no website at all. Target's own app (44.7M installs) and Ford's
FordPass list only a support address, @target.com and @ford.com. When a listing carries
no website, the actor matches on its support-email domain instead and says so with
matched_on: support_email; without this, target.com answered "no". A listing that does
name a website must match on it, unless you set matchOnSupportEmail, because an agency can
put its own site on a client's app.
One company can publish under several developer accounts. Ford Motor Co. and Ford Motor
Credit Company both point at ford.com. After the first match the actor checks up to two
more brand-named developers and reports the one whose top app has the most installs —
FordPass (13.4M), not Ford Credit (285K). A domain with no other brand-named developers
costs nothing extra.
Known limitations, measured
A false is "not found", not "does not exist". Play's search ranks by text relevance,
not ownership, so a company's own app is not guaranteed to appear for its domain. The
actor reads Play's hero "top result" card as well as the result grid, which is where an
exact first-party match almost always sits, and it checks candidates in the order Play
returned them. That covers the common case; it cannot prove a company has no app.
Until 2026-10-07 this section claimed github.com was an unreachable false negative.
That was wrong, and it was this actor's bug rather than a limitation of Play: GitHub's own
app is the hero result, and the parser was reading only the grid beneath it. Four domains
(airbnb.com, craigslist.org, github.com, irs.gov) answered false because of it.
All four now answer correctly, in 3 requests rather than 11.
International domains need their full suffix. acme.com.pl and toko.acme.co.id
reduce to acme.com.pl and acme.co.id, not to com.pl and co.id. A bare public
suffix (com.pl on its own) names no company and comes back as an unbilled error row
telling you so, rather than being silently merged with other inputs.
Every row joins back to your input. query is the exact string you submitted, so a
500-row input gives a 500-row output in the same order; query_domain shows the
normalised domain the match actually used. news.ycombinator.com and ycombinator.com
stay separate rows.
Results are scoped to one Play storefront. Ratings differ by region — Slack is 4.643
in us and 4.333 in jp — as do availability and install counts. The rating columns are
named top_app_rating_in_region / rating_in_region and every row echoes the region it
came from, so a cross-region difference reads as a different storefront rather than as
instability. Comparing rows from different regions compares different storefronts.
Shared domains cannot be joined on website or email alone. Thousands of independent
developers list their GitHub profile as their homepage, so a github.com search surfaces
third-party developers whose listed website is github.com. For code hosts, social networks, site builders, free app hosts and
free mail providers, the actor requires the developer name to corroborate, and sets
domain_is_developer_platform. Enabling matchOnSupportEmail will not join on a free-mail
address at all. Both lists are maintained denylists, not complete facts.
A developer page does not always exist. The developer name on a listing does not always
resolve to a developer page — /store/apps/developer?id=Strava%2C%20Inc. is a live 404.
The match still stands, because it comes from the listing's own website field; the row
reports the matched app as the top app with developer_app_count empty, and is billed at
the match rate rather than the premium.
A deep "no" is reported as provisional. When fewer candidates were checked than the
search returned, or the per-domain request ceiling was hit, the row carries a hint saying
so with the counts, and budget_exhausted. Read the hint column before treating a
false as final.
EU regions are not a supported configuration. EU-region proxies may hit a consent
interstitial. The actor handles one — a redirect to a consent or /sorry URL is treated as
a block and answered by rotating to a fresh session — but whether rotation escapes consent
when every exit in the pool is in the EU is untested. Use a US exit; if you need EU data,
test a small batch first.
Fields can go missing if Google reshapes its pages. Every value is read positionally
out of an undocumented payload embedded in the page. The parsers are written to fail loudly
rather than quietly: if a page does not have the expected shape, the row carries an error
instead of a confident answer, and error rows are never charged. A single unreadable
listing is tolerated and the check moves on; if no listing for a domain can be read at all,
the row is an error with has_app empty, not a false. A scheduled canary checks the
parsers against known listings daily, but it cannot detect a change in Play's ranking —
if Google stopped surfacing companies' own apps for their domains, every check would still
look healthy while answers quietly got worse.
No change-monitoring mode. Each run is a point-in-time check; there is no "only new since last run" option yet.
Pricing
One event per outcome, because the outcomes cost very different amounts to produce. FREE-tier prices shown; the platform's Pricing tab lists the volume tiers, which fall to $0.0007 / $0.0014 / $0.0028 at GOLD and above.
| Event | FREE | When |
|---|---|---|
app-no-match | $0.001 | domain checked, no developer claims it |
app-check | $0.002 | matched, developer's catalogue could not be read |
app-detail | $0.004 | matched, catalogue read, exact installs and dates present |
app-listing | $0.001 | one row of an app-search dump |
The cost asymmetry, stated plainly: a no-match is the cheapest row to buy and the most
expensive to produce (~11 requests versus 4–5 for a match). That is deliberate — a false
is absence of evidence, so charging match price for it would be selling a weaker answer at
the same rate.
Rows that failed, were blocked, or could not be parsed are written to the dataset so the failure is auditable, and carry no charge. Brand-name input, which returns an explicit non-answer, is also free.
Output
Results are available as JSON and CSV from the run's dataset, via the API, and over MCP.
The App checks dataset view puts has_app and hint side by side so a provisional
answer is visible in a spreadsheet export, not just in the API payload.