close

DEV Community

Barış Güngör
Barış Güngör

Posted on

I was tired of opening App Store Connect just to check yesterday's numbers — so I built my own dashboard

Every morning, the same ritual. Open App Store Connect. Wait for it to load. Navigate to Sales and Trends. Wait again. See three numbers. Close it.
That was my life as an indie iOS developer for months. I have two apps on the App Store — a subscription tracker and a sales analytics tool I eventually built for myself — and checking daily stats felt like more work than it should.
So I built AppConsol. And this is what I learned along the way.

The constraint I set myself
No backend. Full stop.
I've seen too many "connect your App Store account" tools that proxy your API key through their servers. Your key, their infrastructure. That never sat right with me.
App Store Connect has a public API. It uses JWT authentication with ES256. Everything runs on-device. So I decided to do exactly that — build the whole thing locally, sign the tokens on-device, call Apple's API directly, store the key in the iOS Keychain.
Here's the core of how JWT generation works in Swift without any third-party libraries:
swiftlet privateKey = try P256.Signing.PrivateKey(pemRepresentation: privateKeyPEM)
let signingInput = "(header.base64URLEncoded()).(payload.base64URLEncoded())"
let signature = try privateKey.signature(for: signingInput.data(using: .utf8)!)
let token = "(signingInput).(signature.rawRepresentation.base64URLEncoded())"
CryptoKit handles everything. No pods, no packages. Apple's own framework, signing Apple's own tokens, calling Apple's own API. It felt right.

The data problem nobody talks about
Apple's Sales Reports API is... quirky.
Weekly reports expect dates formatted as the Monday of that week. Monthly reports expect YYYY-MM, not YYYY-MM-01. If you get it wrong, you get a 400. No explanation in the error. I spent an embarrassingly long time figuring out that 2026-03-01 doesn't work for monthly — it has to be 2026-03.
Also: developerProceeds is per unit. You have to multiply by units. customerPrice is what the user paid. developerProceeds is what you actually get. Don't mix them up or your revenue numbers will be wildly off.

Adding ASO scoring — because I kept paying for tools I didn't need
Every ASO tool I tried cost $30–100/month and showed me a number without explaining why.
So I built a scoring system into AppConsol. 10 rules across three layers:

Metadata layer: Is your title using all 30 characters? Are you repeating keywords across title, subtitle, and keyword field? (Apple indexes all three — repeating wastes characters.)
Cross-data layer: Does your top market have a localized listing? If you're getting downloads from Germany and you have no German metadata, you're invisible to German searchers.
Freshness layer: When did you last update your keywords? App Store algorithms reward fresh metadata.

The rule that surprises most developers: keyword repetition. If your title says "Sales Analytics" and your keyword field also has "sales" and "analytics", you've wasted 15 characters. Apple already indexes your title — the keyword field is for additional terms only.

Three App Store rejections in one launch
The path to shipping was not smooth.
Rejection 1: Wrong IAP product ID. I had com.connectpanel.premium.lifetime hardcoded but the product in App Store Connect was com.appconsol.premium.lifetime. Classic.
Rejection 2: PrivacyInfo.xcprivacy was missing the NSPrivacyAccessedAPICategoryFileTimestamp entry. I use FileManager.temporaryDirectory for CSV exports. Apple now requires you to declare this. The error message from App Store Connect was cryptic — "invalid API category declaration" — but the fix was straightforward once I decoded it.
Rejection 3 (this one stung): Guideline 3.1.2(c). I added monthly and yearly subscriptions in an update and forgot that auto-renewable subscriptions require a Terms of Use link in the app description. My paywall had the link. The description didn't. Five minutes to fix, two days to wait for re-review.

The architecture decision I'm glad I made
Everything is MVVM. DashboardViewModel owns all the business logic — fetching, caching, insight generation. Views just render what they're given.
For caching, I have three layers:

Memory cache — instant tab switches
Disk cache — data survives app restarts
Immutability awareness — yesterday's data never changes, so once it's cached, I never re-fetch it

The insight engine runs 17 rules on your sales data. Some examples:

"The US is growing 45% week-over-week — localize your screenshots"
"Revenue per install is $0.38 — your paywall might be too aggressive"
"Weekdays outperform weekends by 35% — shift your ad spend"

None of this requires a server. All of it runs in a few milliseconds on-device.

What I'd do differently
More unit tests earlier. The date calculation logic for weekly reports (find the most recent Sunday, because Apple uses Sun–Sat fiscal weeks) broke twice in subtle ways. A few edge case tests would have caught both.
Ship sooner. I spent weeks polishing features nobody had asked for yet. The first version that went live was still too big. The ASO engine could have been a separate release.
Talk to other indie developers earlier. The App Store Connect API documentation has gaps. Other developers who've hit the same walls have answers. I found most of my solutions in forum threads and GitHub issues, not in Apple's docs.

Where it is now
AppConsol is live on the App Store. Free tier covers yesterday's data and 7-day trends. Premium unlocks everything — longer ranges, all charts, country breakdowns, full ASO suite.
If you want to try it: I'm sharing a free 1-month premium code for anyone reading this.
Code: 1MONTH
Redeem it from Settings inside the app, or directly in the App Store.
If you build apps and you've found your own solutions to App Store Connect's quirks, I'd genuinely love to hear them in the comments.

AppConsol is available on the App Store. Built with SwiftUI, CryptoKit, and a healthy disrespect for unnecessary infrastructure.
App Store Link: https://apps.apple.com/us/app/appconsol-sales-analytics/id6761332241

Top comments (0)