How it works
Three ways to run
runAudit() branches three ways depending on what you’ve given it:
| Mode | Trigger | What runs |
|---|---|---|
| Live | Credentials configured (.p8 + Key ID + Issuer ID) | The full App Store Connect API sweep, plus local project analysis, plus bundle-id / version cross-checks. |
| Local-only | A project selected, no credentials | Only the local static analysis checks (see Xcode Project). |
| Demo | Neither | A bundled fixture (CircleMind sample) so you can explore the UI offline. |
Authentication
- Base URL:
https://api.appstoreconnect.apple.com/v1. - Auth is a JWT bearer token, algorithm ES256, signed with your downloaded
.p8private key using Apple’s CryptoKit (P256.Signing). - Tokens are capped by Apple at 20 minutes; AuditStore uses ~19 and caches the token in memory, regenerating when it’s older than ~15 minutes.
- You generate the key in App Store Connect → Users and Access → Integrations → App Store Connect API (needs Admin or App Manager role). The
.p8can only be downloaded once.
Following relationship links
The API is JSON:API. Responses carry data, attributes, and relationships — and each relationship carries a full links.related URL.
AuditStore follows those relationship links rather than hardcoding nested endpoint paths. This keeps the app working even if Apple reshuffles secondary endpoints. The entry point is a single lookup:
GET /apps?filter[bundleId]=<your bundle id>From that one app record, every other section is reached by following links: appInfos, appStoreVersions, appPriceSchedule, subscriptionGroups, and so on.
How a check can’t lie
A check that can’t be evaluated becomes an .errored row carrying the raw App Store Connect message. It never crashes, and — critically — it never silently passes. An unevaluated section is surfaced as an error, not a green check.
- 401 / auth failure on the entry-point lookup blocks every ASC-derived section with the same error, rather than leaving them misleadingly empty.
- 404 usually means not created yet — often a finding, not a crash.
- Explicit JSON
nullis distinguished from an absent key.
The scan lifecycle
The live run advances through ordered stages so the progress UI moves monotonically:
- Authenticating — sign the JWT, look up the app by bundle id.
- Fetching metadata — app info, age rating, privacy, category, pricing, encryption.
- Screenshots — per-locale metadata and screenshot sets for the latest editable version.
- Subscriptions — subscription groups (if enabled in settings).
- Build & compliance — attached build, processing state, export compliance, and review details.
- Compiling — aggregate into the final
Report.
Local static analysis
The macOS sandbox can’t spawn xcodebuild, so the analyzer parses project.pbxproj directly (via PropertyListSerialization, which reads the OpenStep format) and walks your Swift/ObjC sources (bounded to 600 files to stay responsive). It resolves the Release build settings for the app target and:
- Detects privacy-sensitive API usage and cross-checks it against your Info.plist purpose strings and your App Privacy declaration.
- Infers an app-type profile (login, tracking, IAP, account system) that gates the Review Guidelines checklist.
- Cross-checks the project’s bundle id and marketing version against the live App Store Connect app.
If a build setting can’t be statically resolved (an .xcconfig base, or unexpanded $(…) variables), the analyzer emits an Info caveat instead of confidently passing or mis-detecting.
Next: Checks →