The problem
Tracking a portfolio of iOS apps — competitors, prospects, or your own releases — sounds trivial until you do it by hand. Rating, review count, price, seller, size, age rating, in-app purchases, OS compatibility, and the latest release notes all live on each app's public App Store page, and copying them into a spreadsheet for dozens of apps, every week, is nobody's idea of a good time.
Building your own scraper is harder than it looks. The App Store page carries two JSON-LD blocks — the first is Organization with no rating at all, the second is SoftwareApplication with the data you actually want — so a parser that grabs "the first ld+json block" returns a confident, success-shaped response with an empty rating at HTTP 200. Several fields (seller, size, age rating, IAPs, compatibility) are not in any structured-data block at all; they sit in a plain-HTML "Information" grid, and release notes live in a separate server-rendered "Version History" panel. Apple's official APIs, meanwhile, want keys, enrollment, and rate-limit management you may not need for a simple watchlist.
What the actor does
The App Store App Intel actor reads the same public app-detail page any browser sees — no login, no Apple API key, no headless browser — and returns one flat JSON row per app. Give it a numeric App Store id (310633997), a slug+id pair, or a full Store URL; all three forms mix freely, up to 100 ids per run, with configurable concurrency (default 5, max 20).
From the README, the behavior that matters:
-
Correct JSON-LD block, proven on every row. It scans every
ld+jsonblock and selects by@type === "SoftwareApplication", never by position, and theevidencefield states which block supplied the data ("block 2 of 2") on every delivered row. -
Eight HTML-fallback fields. Seller, size, age rating, in-app purchases, and compatibility are parsed from the page's plain-HTML Information grid;
whatsNew,version, andreleaseDatecome from the server-rendered Version History panel — no JavaScript execution needed. -
Honest three-state contract. Every row is
found,not_found(Apple's own confirmed HTTP 404), orerror(blocked, timed out, unparseable) — and onlyfoundrows are billed.not_foundanderrorrows are still written to the dataset with the reason, free. -
Honest partials. If an HTML field is genuinely absent — the README's real Spotify row has
inAppPurchases: null— the row ispartial: truewithconfidence: "medium"instead of an invented value. Long Apple display texts are capped and truncated with a visible…marker. -
Guards. URL input is hostname-checked against
apps.apple.comexactly (never a substring), DNS is resolved and pinned against private/reserved ranges (SSRF/DNS-rebinding guard), responses are byte-capped at 3,000,000 with atruncatedflag, and retries apply only to network failures, 429, and 5xx — never to a 404.
Region is fixed to the /us/ storefront — the only region verified live during the build. Reviews and keyword search are permanently out of scope because the relevant hosts' robots.txt disallow those paths.
Example: input and output
Input is minimal:
{
"appIds": ["310633997"],
"maxConcurrency": 5
}
The README ships real, verbatim run output. The happy path for WhatsApp (id 310633997), trimmed:
{
"appId": "310633997",
"status": "found",
"evidence": "ld+json SoftwareApplication (block 2 of 2) + html fallback (8/8)",
"confidence": "high",
"partial": false,
"error": null,
"title": "WhatsApp Messenger",
"ratingValue": 4.7,
"reviewCount": 18424969,
"priceText": "0 USD",
"publisher": "WhatsApp Inc.",
"seller": "WhatsApp Inc.",
"sizeText": "392.2 MB",
"whatsNew": "We update the app regularly to fix bugs, optimize performance and improve the experience. Thanks for using WhatsApp!",
"version": "26.32.73",
"releaseDate": "2026-08-17"
}
A nonexistent id returns status: "not_found" with evidence: "http 404 (App Store's own \"Not Found\" page)" — free. A blocked host returns status: "error" with error: "blocked host \"evil.example\" — only apps.apple.com URLs are accepted" before any request leaves the actor — also free. Each run also writes a one-time KVS OUTPUT summary with requested/delivered/paid/free/failed counts and replay-safety state.
Pricing and the free limit
Pay-per-event: $0.005 per run start plus $0.002 per delivered app (result-found), with subscription discounts on paid Apify plans (the Store headline works out to $1.70 per 1,000 delivered apps at the entry tier). At list prices, a 100-app watchlist run costs $0.005 + 100 × $0.002 = $0.205 — and a run where 8 of 10 ids resolve is billed for 8 delivered apps, not 10. Apify's free plan includes $5 of usage credits per month, covering about 24 full 100-app runs — roughly 2,500 delivered app records — before you pay anything.
Try it
Paste a handful of competitor ids, check the evidence and status fields on the rows, then schedule the watchlist: App Store App Intel.
For AI agents and MCP
The actor takes JSON in and returns structured JSON via the Apify API, and the README describes an agent/MCP pattern explicitly: callable from the Apify API, the SDK, or the Apify MCP server, with the flat status field designed to be branched on directly — found means real data (check partial for completeness), not_found means the app genuinely does not exist right now, and error means retry later or surface the error string. For change tracking, agents diff version, releaseDate, priceText, or ratingValue between scheduled runs, since the actor is stateless per run.
Top comments (0)