Get competitor case file
Read one competitor app’s whole overview in one request.
Download Markdown ↓GEThttps://api.peekpanda.com/v1/competitors/{platform}/{storeId}/case-file
Request
Identify the listing and, optionally, the chart the standing is read from.
Parameters
platformpathiosYesThe preview supports iOS only.storeIdpathnumeric stringYesApple App Store identifier.countryquerytwo-letter storefront codeNoChart storefront; defaults to us.categoryIdqueryall or App Store genre idNoChart category; defaults to all.chartTypequeryfree, paid or grossingNoChart for the standing. When omitted, a priced app reads the paid chart and a free app the free chart.Response
listing.currentFacts holds the name, rating, rating count and price to show, each with its provenance (observed from an App Store storefront, or the catalogue snapshot) and its own asOf date; the top-level listing fields keep the dated snapshot. paid is ready, updating (a rebuild is pending and nothing is stored yet), none (no paid evidence, with the reason) or unavailable (its read failed). A ready summary is read at request time over the last 7 days and 12 weeks, and its attribution splits the ads by basis. startedLast7Days counts ads that started on the reading day or the six UTC days before it; recentDays lays out those same seven days one by one, newest first, with quiet days as zero: newAds is how many ads started that day and liveAds how many of those are still live, and the seven newAds add up to startedLast7Days. weeklyStarts counts new ads per Monday week, oldest first, ending with the reading day’s own partial week. latestAds (newest first) and longestRunningAds (live ads with a start date, earliest start first, empty when none) use the Search paid ads item shape with short-lived media URLs. aso, estimates and rankStanding are null outside iOS; grossing is null when the chart is already grossing. listingHistory.storefronts lists every App Store storefront with an earlier iPhone screenshot set, the most recently changed first; each carries its currentScreenshotUrls and its earlier sets, most recently replaced first, with up to 12 earlier sets across all storefronts. PeekPanda keeps a set when a store lookup returns different iPhone screenshots: replacedAt is the lookup that first saw the new set and lastSeenAt the last lookup that still saw this one, so the developer changed the page between them. History starts at the first change PeekPanda saw. viewer is yours alone. See Section states for sections whose read failed.
Response fields
listing.platformstringlisting.storeIdstringlisting.appNamestringlisting.categorystringlisting.developerIdstringlisting.pricenumberlisting.adSupportednulllisting.inAppPurchasesbooleanlisting.totalRatingsnumberlisting.ratingnumberlisting.installsobjectlisting.releaseDatestringlisting.storeUpdatedAtstringlisting.sourceUpdatedAtstringlisting.descriptionUpdatedAtnulllisting.descriptionnulllisting.shortDescriptionnulllisting.sourcestringlisting.snapshotDatestringlisting.currentFactsobjectpaid.kindstringpaid.summaryobjectpaid.advertisers[].advertiserIdstringpaid.advertisers[].externalIdstringpaid.advertisers[].namestringpaid.advertisers[].totalAdsnumberpaid.advertisers[].liveAdsnumberpaid.latestAdsarraypaid.longestRunningAdsarraypaid.builtAtstringpaid.updatingnullaso.positionKindstringaso.storeIdstringaso.countrystringaso.languagestringaso.sourcestringaso.queriesMatchednumberaso.summaryobjectaso.bestPositions[].querystringaso.bestPositions[].positionnumberaso.bestPositions[].popularitynumberestimates.dataarrayrankStanding.countrystringrankStanding.categoryIdstringrankStanding.chartTypestringrankStanding.chartobjectrankStanding.grossingnullrankStanding.storefronts[].countrystringrankStanding.storefronts[].daystringrankStanding.storefronts[].ranknumberlistingHistory.storefronts[].countrystringlistingHistory.storefronts[].currentScreenshotUrls[]string[]listingHistory.storefronts[].versions[].replacedAtstringlistingHistory.storefronts[].versions[].lastSeenAtstringlistingHistory.storefronts[].versions[].versionstringlistingHistory.storefronts[].versions[].screenshotUrls[]string[]organic.matchedCountnumberorganic.candidateLimitReachedbooleanorganic.latest[].sourceIdstringorganic.latest[].productNamestringorganic.latest[].captionstringorganic.latest[].linkstringorganic.latest[].datePublishedstringorganic.latest[].matchobjectviewer.canGenerateIntelligencebooleanviewer.requestsobjectAuthentication
Requires Bearer API credential or OAuth access token. Scope context:read. MCP calls get_app with action case_file.
Errors
400invalid_request for an invalid filter, identity or limit.401authentication_required for a missing or invalid credential.402subscription_required when access is not entitled.403authorization_required for a missing scope or unavailable preview access.403workspace_not_accessible when x-genviral-tenancy names a workspace the credential cannot reach.404not_found when the record is outside the accessible catalogue.429rate_limited; honor Retry-After when present.429daily_call_limit; the plan's daily API and MCP calls are used until 00:00 UTC.MCP request
Call get_app with action case_file. Path fields go in params, filters in query, and a write body in body.
{
"name": "get_app",
"arguments": {
"request": {
"action": "case_file",
"params": {
"platform": "<platform>",
"storeId": "<storeId>"
}
}
}
}
Source
The stored per-app paid signal (an ad belongs to the app its own verified App Store link names, or to the app whose proven advertiser page ran it), current US App Store search observations, PeekPanda estimates, stored Apple chart snapshots and the public organic corpus. Missing evidence is null or none, never zero.
Refresh
This read returns stored observations and does not start collection.
How paid ads reach an app
Every paid count covers all of the app’s ads, and paid.summary.attribution splits total and live ads by basis. appStoreLink: the ad itself links to the app on the App Store. sellerDomain: the ad ran on the app’s own advertiser page, matched by the website its App Store listing publishes and the page name, and lands on that website. operator: the page was confirmed by PeekPanda review. The three add up to totalAds and liveAds. An ad belongs to one app at most: an ad that links another app counts for that app. A name alone never attributes a page.
Why paid can be none
paid.kind none says why PeekPanda shows no paid counts, read from stored collection state. reason is not_searched (PeekPanda has not looked for the app’s advertiser yet), searching (a search runs now), no_proven_advertiser (the latest search proved no advertiser page; lastSearchedAt says when), collecting (the app’s page is proven and its ads are on the way), advertiser_has_no_ads (the proven page’s latest walk found no Meta ads) or advertiser_under_review (the page is held back pending review). advertiser names the proven page, or is null. none never means the app does not advertise.
{
"kind": "none",
"reason": "advertiser_has_no_ads",
"lastSearchedAt": "2026-09-06T00:00:00.000Z",
"advertiser": {
"externalId": "100000000000001",
"name": "Example Calorie Tracker"
}
}
Section states
Each section says why it holds no evidence. null means the section does not apply to this listing: aso, estimates, rankStanding and listingHistory are null outside iOS. none means PeekPanda holds no paid evidence for the app, with the reason. unavailable means the section could not be read for this request. It says nothing about the evidence itself, so retry later instead of reading it as empty. paid, aso, estimates, rankStanding, listingHistory, organic and viewer.requests can each be unavailable while the rest of the case file still answers; the listing is the only part whose failure fails the request. The example shows a case file whose paid, estimates, organic and request reads failed.
{
"listing": {
"platform": "ios",
"storeId": "0000000000",
"appName": "Example Calorie Tracker",
"category": "Health & Fitness",
"developerId": "example-developer",
"price": 0,
"adSupported": null,
"inAppPurchases": true,
"totalRatings": 12450,
"rating": 4.7,
"installs": {
"kind": "ratings_proxy",
"value": 622500
},
"releaseDate": "2024-05-01",
"storeUpdatedAt": "2026-09-06",
"sourceUpdatedAt": "2026-09-06",
"descriptionUpdatedAt": null,
"description": null,
"shortDescription": null,
"source": "itunes",
"snapshotDate": "2026-03-01",
"currentFacts": {
"appName": {
"provenance": "observed",
"value": "Example Calorie Tracker",
"asOf": "2026-09-06T00:00:00.000Z",
"country": "us"
},
"rating": {
"provenance": "observed",
"value": 4.8,
"asOf": "2026-09-06T00:00:00.000Z",
"country": "us"
},
"totalRatings": {
"provenance": "observed",
"value": 13210,
"asOf": "2026-09-06T00:00:00.000Z",
"country": "us"
},
"price": {
"provenance": "snapshot",
"value": 0,
"asOf": "2026-09-06"
}
}
},
"paid": {
"kind": "unavailable"
},
"aso": {
"positionKind": "observed_search_api_position",
"storeId": "0000000000",
"country": "us",
"language": "en_us",
"source": "itunes_search",
"queriesMatched": 1,
"summary": {
"top3": 0,
"top10": 1,
"top50": 1,
"bestPosition": {
"query": "calorie tracker",
"position": 4
},
"medianPosition": 4
},
"bestPositions": [
{
"query": "calorie tracker",
"position": 4,
"popularity": 63
}
]
},
"estimates": {
"kind": "unavailable"
},
"rankStanding": {
"country": "us",
"categoryId": "all",
"chartType": "free",
"chart": {
"chartType": "free",
"latest": {
"day": "2026-09-06",
"rank": 18
},
"previous": {
"day": "2026-09-05",
"rank": 21
}
},
"grossing": null,
"storefronts": [
{
"country": "us",
"day": "2026-09-06",
"rank": 18
},
{
"country": "gb",
"day": "2026-09-06",
"rank": 33
}
]
},
"listingHistory": {
"storefronts": [
{
"country": "us",
"currentScreenshotUrls": [
"https://is1-ssl.mzstatic.com/image/thumb/example-now/392x696bb.jpg"
],
"versions": [
{
"replacedAt": "2026-08-14T09:30:00.000Z",
"lastSeenAt": "2026-08-13T09:30:00.000Z",
"version": "4.1.3",
"screenshotUrls": [
"https://is1-ssl.mzstatic.com/image/thumb/example-old/392x696bb.jpg"
]
}
]
}
]
},
"organic": {
"kind": "unavailable"
},
"viewer": {
"canGenerateIntelligence": true,
"requests": {
"kind": "unavailable"
}
}
}
AI agent or LLM? Read this page as markdown