We wired the App Store Connect API into an internal dashboard so we could see downloads without waiting for Apple's once-a-day report. Four things in that API behave in ways the documentation does not prepare you for. Each one cost us hours, so here they are in full.
1. Sales reports return 406 until you ask for gzip
The obvious request to /v1/salesReports fails. You authenticate correctly, your
JWT is valid, every parameter is right — and you get HTTP 406 Not Acceptable with no
useful body.
The reason is that this endpoint does not return JSON. It returns a gzipped TSV file, and it
refuses the request unless your Accept header says you will take one:
Accept: application/a-gzip, application/json
That a-gzip media type is not a typo and it is not a standard MIME type. Miss it
and you will spend an afternoon re-checking your ES256 signing code, which is exactly what we did.
2. The sort parameter is rejected on appStoreVersions
Most collection endpoints in this API accept sort. /appStoreVersions
does not — passing it returns 400 rather than ignoring it. Fetch the collection unsorted
and pick the entry whose state is READY_FOR_SALE yourself.
The general lesson: query parameters that work on one collection are not guaranteed on the next. Treat each endpoint as its own API.
3. Product type identifiers decide whether your numbers are real
This is the one that actually matters, because getting it wrong does not throw an error — it just gives you numbers that are wrong in your favour, which is the worst kind of bug.
Every row in a sales report carries a Product Type Identifier, and summing units without reading it counts updates and re-downloads as new installs. The prefixes:
1andF— first-time download3— redownload (same account, downloading again)7— updateIA— in-app purchase or subscription event
We classified by prefix and then checked a 30-day window against the figure shown in App Store Connect's own interface: 800 against Apple's 797. Close enough to trust; the small gap is timezone boundaries at the edges of the window.
One trap inside the trap: 3F starts with a 3, not an F. Order your prefix checks so
that redownloads are tested before the F case, or every redownload lands in your
first-install bucket.
4. Group by Apple ID, not by app name
We renamed an app. For weeks afterwards our totals showed it twice — once under each name — and the sum looked healthy while the truth was half of it.
The app name in a sales report is whatever the app was called on that day. The stable key is the Apple Identifier. Group on that and keep the most recent name for display.
Metadata endpoints need a different key
Worth knowing before you build: a Sales-role API key reads reports fine but returns 403 on
appInfos and appStoreVersionLocalizations. If you only need public
metadata — icons, descriptions, screenshots — the unauthenticated iTunes lookup endpoint is simpler
and has no role requirements.
Related: as of mid-2026 that lookup endpoint returns an empty screenshotUrls
array for many apps regardless of country parameter. We tested four variants and got zero every
time. If you need screenshots programmatically, the App Store product page is currently the only
reliable source.