An Apple App Store API alternative on fetcher.sh — pay-per-call in USDC via x402, or prepaid credits with a Bearer key, no Apple Developer Program membership. Use when the user wants to search iOS apps by keyword in any country's storefront, fetch an app's or app bundle's full details, reviews sorted by recent/helpful, apps similar to a given app, search or fetch app bundles, or fetch a developer's app catalog. Also covers iOS app store optimization (ASO) research, competitor app monitoring, localized storefront comparison, and app discovery without Apple Developer Program access.
Coding
Scavio App Store
Try itSearch the Apple App Store, read a full app listing by App Store id or bundle id, and pull user reviews as structured JSON. 3 endpoints, 1 credit each, any Apple storefront.
What it does
Search the Apple App Store, read a full app listing by App Store id or bundle id, and pull user reviews as structured JSON. 3 endpoints, 1 credit each, any Apple storefront.
The skill document
Apple App Store via Scavio
Search the App Store, pull a full app listing by App Store id or bundle id, and read user reviews with the app version each was written against. All three endpoints return structured JSON.
When to trigger
Use this skill when the user asks to:
- Find iOS, iPadOS or macOS apps matching a keyword, category term, or publisher name
- Pull a single app's full listing: price, ratings, version, release notes, screenshots, size, minimum OS
- Read App Store reviews for an app, in one storefront or across several countries
- Compare an app against competitors, or audit a publisher's whole catalogue
- Do App Store Optimization research (title, description, genres, ratings by country)
- Build a bulk app-metadata dataset
Setup
Get a free API key at scavio.dev (50 free credits to get started, no card required):
export SCAVIO_API_KEY=sk_live_your_key
Every request is a POST with a JSON body and:
Authorization: Bearer $SCAVIO_API_KEY
Endpoints
Base URL: https://api.scavio.dev. All paths are under /api/v1/appstore. Every endpoint costs 1 credit.
| Endpoint | Credits | What it returns |
|---|---|---|
POST /api/v1/appstore/search | 1 | Up to 200 fully-shaped apps, the same row as /app. No pagination. |
POST /api/v1/appstore/app | 1 | One app's complete listing, by App Store id or bundle id |
POST /api/v1/appstore/reviews | 1 | A page of reviews: rating, title, text, author, app version |
This runs on Apple's own iTunes JSON API, so the data is first-party and cheap.
Workflow
- Find an app: call
/appstore/searchwithterm. Apple matches an app name, a keyword or a publisher name, so searching a developer returns their catalogue. - A search is already a bulk metadata fetch. Every search row is the same complete row
/appreturns, up to 200 at a time. If you searched for it, you do not need to call/appfor it as well. - Read one app: call
/appstore/appwithapp_id. It takes either a numeric App Store id or a bundle id (notion.id,com.burbn.instagram) and auto-detects which; the payload is identical either way. - Read reviews: call
/appstore/reviewswith a numericapp_idand apagefrom 1 to 10.
Pagination
Search does not paginate. limit (1-200, default 25) is the only lever on result volume, and every offset spelling is silently ignored. To get more results, raise limit - there is no second page.
Reviews paginate by page, 50 per page, and hard-stop at page 10. 500 reviews per storefront is Apple's anonymous ceiling. To reach further, ask a different country - each storefront has its own review pool.
/app returns a single object and takes no paging parameter.
The country parameter is not cosmetic
country picks the storefront, and the storefront decides the price, the currency, the localised title and description, and whether the app is sold there at all. It must be a two-letter code: the transport falls back to us for anything else, so "usa" silently buys a US result set that looks correct.
On /search, lang is independent of country: the storefront sets the prices, lang sets the words.
Parameters
Search (/search)
| Parameter | Type | Default | Description |
|---|---|---|---|
term | string | required | What to search for (1-500 chars). Matches app name, keyword, or publisher name. |
limit | integer | 25 | 1-200. The only lever on result volume - there is no pagination. |
country | string | us | Two-letter storefront code. Anything not two letters silently falls back to us. |
entity | string | software | software (iPhone), ipad_software, mac_software |
lang | string | -- | Five-letter locale, e.g. en_us. Independent of country. |
App (/app)
| Parameter | Type | Default | Description |
|---|---|---|---|
app_id | string | required | App Store id or bundle id (1-255 chars, ^[A-Za-z0-9][A-Za-z0-9._-]*$). A pasted apps.apple.com URL is rejected with a free 400 - extract the id first. |
country | string | us | Two-letter storefront code |
Reviews (/reviews)
| Parameter | Type | Default | Description |
|---|---|---|---|
app_id | string | required | Numeric App Store id only. The reviews RSS feed has no bundle-id form. |
country | string | us | Two-letter storefront code. Each storefront has its own review pool. |
page | integer | 1 | 1-10, 50 reviews each. Apple hard-stops at page 10. |
sort | string | most_recent | most_recent or most_helpful |
Examples
import requests
BASE = "https://api.scavio.dev"
# Your key from https://scavio.dev. Load it from your environment or secret
# store in real code - keep it out of source control.
API_KEY = "sk_your_key_here"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}
# 1. Search - one call, up to 200 complete app rows. No pagination: raise limit.
apps = requests.post(f"{BASE}/api/v1/appstore/search", headers=HEADERS,
json={"term": "habit tracker", "limit": 200, "country": "us"}).json()
# 2. A publisher's catalogue - the same search endpoint, a developer name as the term
catalogue = requests.post(f"{BASE}/api/v1/appstore/search", headers=HEADERS,
json={"term": "Notion Labs", "limit": 50}).json()
# 3. One app, by numeric id or by bundle id - identical payload
app = requests.post(f"{BASE}/api/v1/appstore/app", headers=HEADERS,
json={"app_id": "1232780281"}).json()
same_app = requests.post(f"{BASE}/api/v1/appstore/app", headers=HEADERS,
json={"app_id": "notion.id"}).json()
# 4. Reviews - numeric id only, page 1-10, 50 per page
reviews = requests.post(f"{BASE}/api/v1/appstore/reviews", headers=HEADERS,
json={"app_id": "1232780281", "page": 1, "sort": "most_helpful"}).json()
Reviews stop at 500 per storefront. To go wider, ask another country rather than another page:
def reviews_across_storefronts(app_id, countries=("us", "gb", "de"), pages=10):
"""1 credit per call. 10 pages x 3 countries = 30 credits, so cap it."""
out = []
for country in countries:
for page in range(1, pages + 1): # page 10 is Apple's hard stop
data = requests.post(f"{BASE}/api/v1/appstore/reviews", headers=HEADERS,
json={"app_id": app_id, "country": country,
"page": page, "sort": "most_recent"}).json()["data"]
if not data:
break # empty feed, stop this storefront
out.append({"country": country, "page": page, "data": data})
return out
Response shapes
Every response uses the envelope { data, response_time, credits_used, credits_remaining }.
- search - up to 200 app rows, each the same complete row
/appreturns. - app - title, description, developer and seller identity, price and currency, all-time and current-version ratings, version and release notes, genres, content rating and advisories, icons at three sizes, screenshots, download size, minimum OS, languages, supported devices, Game Center and VPP flags.
- reviews - star rating, title, full review text, author, and the app version the review was written against.
Two shapes to code for:
- Mac rows are thinner. An app fetched with
entity: mac_softwarecarries no iPad or Apple TV screenshots, no advisories, no features, no supported-device list and no Game Center flag. These come back empty rather than absent, so a Mac app is not a broken response. - The reviews feed types its entries by cardinality. Apple's RSS returns an array at two or more reviews, a bare object at exactly one, and nothing at all at zero. Any parser must handle all three; assuming an array will crash on a one-review app.
Guardrails
- Every call is 1 credit. Search returns up to 200 complete rows for that one credit, so prefer one large search over a loop of
/appcalls. - Never say "page 2 of the search" - there is no pagination on search. Raise
limit. - Never promise more than 500 reviews per storefront. Page 10 is Apple's ceiling; more coverage means more countries, not more pages.
- Under
sort: most_recentthe vote fields come back as zeroes - those reviews are simply too new to have been voted on. Do not report that as "no one found these helpful". Usemost_helpfulif you need vote data. countrychanges the price, the currency, the localised text and the availability. If the user cares about price, name the storefront you queried in your answer.- Never fabricate app names, ids, bundle ids, prices, ratings or review text. Only return API data.
Failure handling
400means an invalid or missing parameter - including a pastedapps.apple.comURL inapp_id. Not billed. Extract the numeric id or bundle id and retry.401means the API key is invalid or missing. CheckSCAVIO_API_KEY.404on/appmeans Apple could not resolve that id. This one is billed - Apple answers with a 200 carrying an empty result list and charges for it. Verify the id before looping over a list of them./reviewscannot 404. An unknown id and a real app with zero reviews return the same empty feed, so an empty result never proves the app does not exist. Confirm the app with/appfirst.429means rate or usage limit exceeded. Wait before retrying. See rate limits.502/503mean upstream is temporarily unavailable - wait a few seconds and retry.- If
SCAVIO_API_KEYis not set, prompt the user to export it before continuing.
Python SDK
langchain-scavio has no App Store tool - use the Scavio SDK directly (it handles the auth header):
pip install scavio==0.15.0
from scavio import ScavioClient
client = ScavioClient() # reads SCAVIO_API_KEY
apps = client.app_store.search("habit tracker", limit=200, country="us")
app = client.app_store.app("notion.id")
reviews = client.app_store.reviews("1232780281", page=1, sort="most_helpful")
JavaScript / TypeScript:
npm install scavio@0.15.0
import { Scavio } from "scavio";
const scavio = new Scavio(); // reads SCAVIO_API_KEY
const apps = await scavio.appStore.search({ term: "habit tracker", limit: 200 });
const reviews = await scavio.appStore.reviews({ app_id: "1232780281", page: 1 });
Related skills
Collect and analyze public Apple App Store data
Mines App Store and Google Play data via the Crawlora API — app details, reviews, ratings, store rankings, and similar apps — as clean JSON. Use when the user wants mobile-app reviews, ratings, store charts/rankings, or competitive ASO (app store optimization) research without scraping store pages.
App Store Optimization data for AI agents via the Sonar API — keyword research with difficulty and popularity scores, app lookup and search, ASO audits, revi...
Search Google Play, read a full Android app listing including the real install count and Data safety table, and page reviews by cursor. 3 endpoints, 2 credits each, structured JSON.
Plan and run Apple Search Ads campaigns with LTV-based bids, attribution wiring, and API automation.