Apple Developer — App Store & TestFlight Guide
API Key
| Field |
Value |
| Key ID |
PPATU9GL73 |
| Issuer ID |
1dbc2980-1621-4fb9-940b-e28257e6322c |
| Role |
App Manager |
| File |
AuthKey_PPATU9GL73.p8 |
⚠️ The .p8 file is NOT checked into git (blocked by secret scanner). It lives at ~/.appstoreconnect/private_keys/AuthKey_PPATU9GL73.p8 on each machine. Copy it manually between machines or re-download from App Store Connect (one-time download only — keep a backup).
Setup on any machine
mkdir -p ~/.appstoreconnect/private_keys
# Copy the .p8 from another machine or your backup
cp /path/to/AuthKey_PPATU9GL73.p8 ~/.appstoreconnect/private_keys/
chmod 600 ~/.appstoreconnect/private_keys/AuthKey_PPATU9GL73.p8
Apple Developer Account
Registered Apps
| Product |
Bundle ID |
App Store Connect |
SKU |
Status |
| LysnrAI |
com.bytelyst.LysnrAI |
✅ Created |
lysnrai |
In TestFlight (build 59) |
| ChronoMind |
com.saravana.chronomind |
❌ Needs creation |
chronomind |
Bundle ID registered, pending app creation |
| ChronoMind Widgets |
com.saravana.chronomind.widgets |
N/A (extension) |
— |
Bundle ID registered |
App Store Connect — Creating a New App
- Go to App Store Connect → My Apps
- Click "+" → "New App"
- Fill in:
- Platforms: iOS
- Name: Product display name (e.g.,
ChronoMind)
- Primary Language: English (U.S.)
- Bundle ID: Select from dropdown (must be registered first — see below)
- SKU: Lowercase product ID (e.g.,
chronomind)
- User Access: Full Access
- Click Create
Registering a New Bundle ID
Via API (preferred)
import jwt, time, json, urllib.request
with open('AuthKey_PPATU9GL73.p8', 'r') as f:
key = f.read()
token = jwt.encode(
{'iss': '1dbc2980-1621-4fb9-940b-e28257e6322c', 'iat': int(time.time()),
'exp': int(time.time()) + 1200, 'aud': 'appstoreconnect-v1'},
key, algorithm='ES256', headers={'kid': 'PPATU9GL73', 'typ': 'JWT'}
)
data = json.dumps({'data': {'type': 'bundleIds', 'attributes': {
'identifier': 'com.saravana.PRODUCT',
'name': 'ProductName',
'platform': 'IOS'
}}}).encode()
req = urllib.request.Request(
'https://api.appstoreconnect.apple.com/v1/bundleIds',
data=data,
headers={'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'},
method='POST'
)
resp = urllib.request.urlopen(req)
print(json.loads(resp.read())['data']['id'])
Via Portal
- Go to Certificates, Identifiers & Profiles → Identifiers
- Click "+" → App IDs → App
- Enter description + bundle ID
- Enable capabilities (App Groups, Push Notifications, etc.)
- Click Register
TestFlight — Uploading a Build
Automated (recommended)
Each product repo has a release-testflight.sh script:
# LysnrAI
cd learning_voice_ai_agent/mobile_app/ios
# (uses manual pbxproj — see .windsurf/workflows/release-testflight.md)
# ChronoMind
cd learning_ai_clock/ios
bash release-testflight.sh
Manual CLI
# 1. Archive
xcodebuild archive \
-project MyApp.xcodeproj \
-scheme MyApp \
-configuration Release \
-archivePath /tmp/MyApp.xcarchive \
-destination 'generic/platform=iOS'
# 2. Export + Upload
xcodebuild -exportArchive \
-archivePath /tmp/MyApp.xcarchive \
-exportPath /tmp/MyApp_export \
-exportOptionsPlist ExportOptions.plist \
-allowProvisioningUpdates \
-authenticationKeyPath ~/.appstoreconnect/private_keys/AuthKey_PPATU9GL73.p8 \
-authenticationKeyID PPATU9GL73 \
-authenticationKeyIssuerID 1dbc2980-1621-4fb9-940b-e28257e6322c
ExportOptions.plist (template)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store-connect</string>
<key>teamID</key>
<string>748N7QPX7J</string>
<key>destination</key>
<string>upload</string>
<key>signingStyle</key>
<string>automatic</string>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>
App Store Submission Checklist
Before submitting for App Store review, ensure:
Required Assets
| Asset |
Spec |
Notes |
| App Icon |
1024×1024 PNG |
No alpha channel, no rounded corners |
| Screenshots (6.7") |
1290×2796 |
iPhone 15 Pro Max — required |
| Screenshots (6.5") |
1284×2778 |
iPhone 14 Plus — required |
| Screenshots (5.5") |
1242×2208 |
iPhone 8 Plus — optional but recommended |
| iPad Screenshots |
2048×2732 |
Required if app supports iPad |
| App Preview Video |
1920×1080 or device res |
Optional, up to 30 seconds |
Required Metadata
| Field |
Notes |
| App Name |
Max 30 characters |
| Subtitle |
Max 30 characters |
| Description |
Required, no max (but first 3 lines matter most) |
| Keywords |
Max 100 characters, comma-separated |
| Support URL |
Must be a valid, reachable URL |
| Privacy Policy URL |
Required — must be a valid, reachable URL |
| Category |
Primary + optional secondary |
| Age Rating |
Answer the questionnaire honestly |
| Copyright |
e.g., © 2026 ByteLyst |
Required for Review
| Requirement |
Details |
| Privacy Policy URL |
Must be publicly accessible |
| App Review Information |
Contact info (name, phone, email) for reviewer |
| Demo Account |
If app requires login, provide test credentials |
| Notes for Review |
Explain any non-obvious features or permissions |
| Content Rights |
Confirm you have rights to all content |
| IDFA Declaration |
If using AdSupport framework |
| Export Compliance |
Encryption usage (most apps: standard encryption = YES, exempt) |
Privacy & Permissions
For each permission your app uses, you must:
- Add a usage description string in Info.plist (e.g.,
NSMicrophoneUsageDescription)
- Declare it in App Store Connect → App Privacy
Common permissions for ByteLyst apps:
| Permission |
Info.plist Key |
Apps |
| Microphone |
NSMicrophoneUsageDescription |
LysnrAI |
| Speech Recognition |
NSSpeechRecognitionUsageDescription |
LysnrAI |
| Location |
NSLocationWhenInUseUsageDescription |
PeakPulse |
| Health |
NSHealthShareUsageDescription |
PeakPulse, NomGap |
| Notifications |
NSUserNotificationsUsageDescription |
All |
| Camera |
NSCameraUsageDescription |
MindLyst |
App Privacy (Data Collection)
In App Store Connect → App Privacy, declare:
- What data types your app collects
- Whether data is linked to user identity
- Whether data is used for tracking
- Purpose of each data type
TestFlight Beta Testing
Internal Testing (up to 100 testers)
- Upload build (see above)
- Wait ~15-30 min for processing
- Go to App Store Connect → TestFlight → Internal Testing
- Add testers by Apple ID email
- Testers install via TestFlight app
External Testing (up to 10,000 testers)
- Create a beta group in TestFlight
- Add a build to the group
- Submit for Beta App Review (required for external testers)
- Review takes ~24-48 hours
- Once approved, distribute via link or email
Beta App Review Requirements
- Same as App Store review but slightly more lenient
- Still needs: privacy policy URL, app description, contact info
- Crashes or obvious bugs will be rejected
Certificates & Signing
| Type |
Purpose |
Managed By |
| Apple Development |
Debug builds, simulator |
Xcode (automatic) |
| Apple Distribution |
App Store / TestFlight builds |
Xcode (automatic with API key) |
With -allowProvisioningUpdates and the API key, Xcode automatically manages certificates and provisioning profiles. No manual certificate management needed.
Troubleshooting
| Error |
Fix |
No signing certificate "iOS Distribution" |
Use -allowProvisioningUpdates with API key |
Authentication failed |
Check .p8 key path, Key ID, Issuer ID |
Error Downloading App Information |
Create app record in App Store Connect first |
App Groups capability |
Register App Group in developer portal |
FORBIDDEN_ERROR on app creation |
API key needs Admin role (App Manager can't create apps) |
Bundle ID not available |
Someone else registered it — use a different one |
Missing compliance |
Set Export Compliance in App Store Connect |
Build processing stuck |
Wait up to 1 hour; check email for processing errors |