App Store Connect Dashboard: Your Sales on Your Phone and Office TV
In about half an hour you can have yesterday's App Store downloads and proceeds as live tiles on your phone and on the office TV, straight from Apple's App Store Connect API. You create an API key in App Store Connect, write one small endpoint that signs a token and downloads the sales report, and dashboardbase renders what it returns.
Be clear about one thing first: dashboardbase doesn't connect to App Store Connect itself. There's no "sign in with Apple" button. Your endpoint holds the API key and talks to Apple; dashboardbase only ever sees the two numbers your endpoint chooses to return. Your private key never leaves your server.
What you'll build
- Downloads (KPI): units from the latest daily sales report.
- Proceeds (KPI): what Apple will pay you for that day, in your proceeds currency.
Both come from one report download, so it's one endpoint returning two widgets' worth of data, or two endpoints sharing a helper.
Prerequisites
- Access to App Store Connect with permission to create API keys (Admin) **
- Your vendor number, which Apple shows under Payments and Financial Reports **
- A backend you can deploy a Node.js endpoint to
- A dashboardbase account (14-day free trial)
Step 1: Create an App Store Connect API key **
In App Store Connect, open Users and Access, then the Integrations tab, and create a team API key. Give it a role that can read Sales and Trends, such as Sales or Finance; it doesn't need anything more. Download the private key (.p8 file) once, since Apple won't show it again, and note two values from the same page:**
- the Key ID, shown in the list of active keys
- the Issuer ID, shown near the top of the page
Store all three as secrets on your server, never in the client or the repo.
Step 2: Sign a token **
The API requires a JSON Web Token signed with your private key using ES256. The header carries your key ID; the payload carries your issuer ID, an issued-at time, an expiry no more than 20 minutes later, and the audience appstoreconnect-v1.** With the jsonwebtoken package:
import jwt from 'jsonwebtoken'
function appleToken() {
const now = Math.floor(Date.now() / 1000)
return jwt.sign(
{ iss: process.env.ASC_ISSUER_ID, iat: now, exp: now + 15 * 60, aud: 'appstoreconnect-v1' },
process.env.ASC_PRIVATE_KEY, // the contents of the .p8 file
{ algorithm: 'ES256', header: { alg: 'ES256', kid: process.env.ASC_KEY_ID, typ: 'JWT' } }
)
}Fifteen minutes stays safely inside Apple's 20-minute limit. You can also add a scope claim limiting the token to the one request you make, which is worth doing for a key that lives on a server.**
Step 3: Download and parse the sales report **
Sales data comes from GET https://api.appstoreconnect.apple.com/v1/salesReports. For the daily summary, pass filter[reportType]=SALES, filter[reportSubType]=SUMMARY, filter[frequency]=DAILY, filter[version]=1_0 and your filter[vendorNumber]. For daily reports the date is optional; leave it out and you get the latest available day.**
The response is a gzip file containing tab-separated values, not JSON:**
import { gunzipSync } from 'node:zlib'
async function latestDailySales() {
const params = new URLSearchParams({
'filter[reportType]': 'SALES',
'filter[reportSubType]': 'SUMMARY',
'filter[frequency]': 'DAILY',
'filter[version]': '1_0',
'filter[vendorNumber]': process.env.ASC_VENDOR_NUMBER,
})
const response = await fetch(`https://api.appstoreconnect.apple.com/v1/salesReports?${params}`, {
headers: { Authorization: `Bearer ${appleToken()}` },
})
if (!response.ok) throw new Error(`App Store Connect responded ${response.status}`)
const tsv = gunzipSync(Buffer.from(await response.arrayBuffer())).toString('utf8')
const [header, ...lines] = tsv.trim().split('\n')
const columns = header.split('\t')
const col = (name) => columns.indexOf(name)
let units = 0
const proceeds = {}
let date = ''
for (const line of lines) {
const cells = line.split('\t')
const rowUnits = Number(cells[col('Units')]) || 0
const perUnit = Number(cells[col('Developer Proceeds')]) || 0
const currency = cells[col('Currency of Proceeds')]
units += rowUnits
proceeds[currency] = (proceeds[currency] ?? 0) + rowUnits * perUnit
date = cells[col('Begin Date')]
}
return { units, proceeds, date }
}Look columns up by name rather than position, so a new column in the report doesn't silently shift your numbers. Developer Proceeds is per unit, so multiply by Units. Refunds appear as negative units and reduce the total.**
Proceeds arrive in several currencies. The code keeps them apart; convert to one currency with a rate you trust, or show your main currency and ignore the rest, but decide deliberately.
Step 4: Return the widgets
The KPI widget wants a title, a headline value and an optional badge. Here's the contract:
{
"title": "Downloads",
"actions": [
{
"title": "View Details",
"type": "link",
"url": "https://appstoreconnect.apple.com"
}
],
"data": {
"header": {
"title": "2,543",
"subtitle": "vs last month",
"badge": {
"text": "+12%",
"icon": "ArrowUp",
"color": "Success"
}
}
}
}And the endpoints, protected with the workspace secret dashboardbase sends on every request:
const SECRET = process.env.DASHBOARDBASE_ENDPOINT_SECRET
// Fail at boot rather than serving sales data unprotected.
if (!SECRET) throw new Error('DASHBOARDBASE_ENDPOINT_SECRET is not set')
app.use('/dashboard', (req, res, next) => {
if (req.get('x-dashboardbase-secret') !== SECRET) {
return res.status(401).json({ error: 'unauthorized' })
}
next()
})
app.get('/dashboard/app-store/downloads', async (req, res) => {
const { units, date } = await latestDailySales()
res.json({
title: 'Downloads',
actions: [{ title: 'View Details', type: 'link', url: 'https://appstoreconnect.apple.com' }],
data: {
header: {
title: units.toLocaleString('en-US'),
subtitle: `App Store, ${date}`,
badge: { text: 'Daily report', color: 'Blue' },
},
},
})
})
app.get('/dashboard/app-store/proceeds', async (req, res) => {
const { proceeds, date } = await latestDailySales()
const usd = proceeds.USD ?? 0
res.json({
title: 'Proceeds',
actions: [{ title: 'View Details', type: 'link', url: 'https://appstoreconnect.apple.com' }],
data: {
header: {
title: `$${usd.toFixed(2)}`,
subtitle: `USD proceeds, ${date}`,
badge: { text: `${Object.keys(proceeds).length} currencies`, color: 'Blue' },
},
},
})
})Apple publishes each daily report once a day, so polling it every minute just repeats the same download. Set the dashboard's refresh to the longest interval, 30 minutes, and cache latestDailySales() on your server for an hour, so both widgets share one call to Apple.
Step 5: Add the widgets
In dashboardbase, add a KPI widget, paste https://your-api.example.com/dashboard/app-store/downloads as the datasource, and test it. The test shows the exact request sent and what came back, and makes one extra request with every credential stripped, so you'll know the endpoint refuses strangers. Repeat for proceeds.
Before that, you can paste a sample response into the public endpoint validator to see it rendered, no account needed.
Step 6: Phone, TV and alerts
- Phone: open the dashboard in the native iOS or Android app.
- Office TV: open
www.dashboardbase.com/tvon the television, approve the code from your phone, and pick the dashboard. Nothing logs in on the screen. How TV pairing works. - Alerts: return an
alertobject when something needs attention, such as downloads falling well below last week, and members who opted in get a push notification.
Going further
- Which numbers to show. The mobile app KPI dashboard covers the app metrics worth a tile.
- Subscriptions. For subscription apps, App Store sales reports are only part of the picture. RevenueCat's API gives MRR and active subscriptions across both stores; see our RevenueCat dashboard tutorial.
- Weekly and monthly. The same endpoint with
filter[frequency]=WEEKLYorMONTHLYand afilter[reportDate]gives longer windows for a line chart.** - Let AI write it. The free Skill teaches Claude Code or Cursor the widget contract, so "add a weekly downloads line chart from App Store Connect" comes back in the right shape.
When this isn't the right approach
- If you want App Store analytics, impressions, product-page views and conversion, those live in App Store Connect's analytics, not the sales report.
- If you'd rather not run an endpoint at all, a subscription-analytics tool that connects to the App Store directly is the simpler route.
About the third-party code **
The App Store Connect examples on this page were written against Apple's public documentation for the App Store Connect API as of 6 October 2026. Third-party APIs change: endpoints, parameters, report columns, key roles and token rules can be renamed or retired after we publish. Treat this code as inspiration and a starting point, and check it against Apple's current API documentation before you rely on it.
Sources
- Download sales and trends reports (Apple), checked 6 October 2026
- Generating tokens for API requests (Apple), checked 6 October 2026