Claude-skill-registry apple-developer-apis
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/apple-developer-apis" ~/.claude/skills/majiayu000-claude-skill-registry-apple-developer-apis && rm -rf "$T"
skills/data/apple-developer-apis/SKILL.md- pip install
Apple Developer APIs
Complete guide for integrating Apple Developer APIs, including App Store Connect API, App Store Server API, Sign in with Apple, and App Store Server Notifications.
Quick Start Overview
Apple provides several REST APIs for managing apps, subscriptions, and authentication:
- App Store Connect API: Manage apps, TestFlight, certificates, and metadata
- App Store Server API: Manage transactions, subscriptions, and refunds
- App Store Server Notifications: Receive webhook events for subscription lifecycle
- Sign in with Apple REST API: Server-side authentication validation
All APIs use JWT (JSON Web Token) authentication with ES256 algorithm.
Authentication (All APIs)
Creating API Keys
- Navigate to App Store Connect > Users and Access > Integrations
- Generate Individual Key or Team Key
- Download the
private key file (only downloadable once).p8 - Note the Key ID and Issuer ID
JWT Structure
// Header { "alg": "ES256", "kid": "YOUR_KEY_ID", "typ": "JWT" } // Payload { "iss": "YOUR_ISSUER_ID", "iat": 1623345678, // Issued at timestamp "exp": 1623346878, // Expiration (max 20 minutes) "aud": "appstoreconnect-v1", "bid": "com.yourcompany.yourapp" // Required for Server API }
Generate JWT (Node.js)
const jwt = require('jsonwebtoken'); const fs = require('fs'); function generateJWT(issuerId, keyId, privateKeyPath, bundleId) { const privateKey = fs.readFileSync(privateKeyPath); return jwt.sign({ bid: bundleId // Include for App Store Server API }, privateKey, { algorithm: 'ES256', expiresIn: '20m', issuer: issuerId, audience: 'appstoreconnect-v1', header: { alg: 'ES256', kid: keyId, typ: 'JWT' } }); }
Generate JWT (Python)
import jwt import time from pathlib import Path def generate_jwt(issuer_id: str, key_id: str, private_key_path: str, bundle_id: str = None) -> str: private_key = Path(private_key_path).read_text() payload = { "iss": issuer_id, "iat": int(time.time()), "exp": int(time.time()) + 1200, "aud": "appstoreconnect-v1" } if bundle_id: payload["bid"] = bundle_id return jwt.encode(payload, private_key, algorithm="ES256", headers={"kid": key_id, "typ": "JWT"})
App Store Connect API
Base URL:
https://api.appstoreconnect.apple.com/v1
REST API for automating App Store Connect tasks.
Key Endpoints
Apps
GET /v1/apps # List all apps GET /v1/apps/{id} # Get specific app GET /v1/apps/{id}/builds # List builds for app
TestFlight
GET /v1/builds # List all builds POST /v1/betaTesters # Add beta tester GET /v1/betaGroups # List beta groups
Certificates & Profiles
GET /v1/certificates # List certificates POST /v1/certificates # Create certificate GET /v1/profiles # List provisioning profiles
In-App Purchases
GET /v1/apps/{id}/inAppPurchases # List IAPs POST /v1/inAppPurchases # Create IAP GET /v1/subscriptionGroups # List subscription groups
Request Example
async function listApps(jwt) { const response = await fetch( 'https://api.appstoreconnect.apple.com/v1/apps?fields[apps]=name,bundleId&limit=10', { headers: { 'Authorization': `Bearer ${jwt}`, 'Content-Type': 'application/json' } } ); return response.json(); }
App Store Server API
Base URL (Production):
https://api.storekit.itunes.apple.com
Base URL (Sandbox): https://api.storekit-sandbox.itunes.apple.com
REST API for managing customer transactions and subscriptions. Replaces deprecated
verifyReceipt.
Key Endpoints
Transaction History
GET /inApps/v1/history/{transactionId}
Returns complete purchase history for a customer.
Query Parameters:
- Pagination tokenrevision
- ASCENDING or DESCENDINGsort
- AUTO_RENEWABLE, NON_RENEWABLE, CONSUMABLE, NON_CONSUMABLEproductType
Subscription Status
GET /inApps/v1/subscriptions/{transactionId}
Returns current status of all subscriptions.
Refund History
GET /inApps/v2/refund/lookup/{transactionId}
Order Lookup
POST /inApps/v1/lookup/{orderId}
Consumption Information
PUT /inApps/v1/transactions/consumption/{transactionId}
Send consumption data for consumable IAPs.
Test Notification
POST /inApps/v1/notifications/test
Response Structure
All responses contain signed JWS data:
{ "signedTransactions": ["eyJhbGciOiJFUzI1NiIsIng1YyI6Wy..."], "revision": "next_page_token" }
Official Libraries
Apple provides official libraries:
# Node.js npm install @apple/app-store-server-library # Python pip install app-store-server-library
Verify signed data:
const { SignedDataVerifier } = require('@apple/app-store-server-library'); const verifier = new SignedDataVerifier( [appleRootCertificate], true, // Enable online checks 'Production', 'com.yourcompany.yourapp', YOUR_APP_ID ); const transaction = await verifier.verifyAndDecodeTransaction(signedTransaction);
App Store Server Notifications V2
Webhooks for subscription lifecycle events.
Setup
- Configure server URL in App Store Connect > App > App Information
- Implement endpoint to receive POST requests
Notification Types
| Type | Description |
|---|---|
| Initial subscription purchase |
| Subscription renewed |
| Renewal failed |
| Subscription expired |
| Transaction refunded |
| Auto-renew toggled |
| Plan changed |
| Billing grace period ended |
| Promotional offer applied |
| Apple requests consumption data |
| Family sharing revoked |
Subtypes
/INITIAL_BUYRESUBSCRIBE
/DOWNGRADEUPGRADE
/AUTO_RENEW_ENABLEDAUTO_RENEW_DISABLED
/VOLUNTARY
/BILLING_RETRYPRICE_INCREASE
Payload Structure
{ "signedPayload": "eyJhbGciOiJFUzI1NiIsIng1YyI6Wy..." }
Decoded Payload
{ "notificationType": "DID_RENEW", "subtype": "BILLING_RECOVERY", "notificationUUID": "unique-id", "data": { "appAppleId": 123456789, "bundleId": "com.yourcompany.yourapp", "environment": "Production", "signedTransactionInfo": "...", "signedRenewalInfo": "..." }, "version": "2.0", "signedDate": 1679529600000 }
Webhook Handler (Node.js)
const express = require('express'); const { SignedDataVerifier } = require('@apple/app-store-server-library'); app.post('/apple/notifications', async (req, res) => { const { signedPayload } = req.body; const verifier = new SignedDataVerifier( [appleRootCert], true, 'Production', bundleId, appId ); const notification = await verifier.verifyAndDecodeNotification(signedPayload); switch (notification.notificationType) { case 'DID_RENEW': await handleRenewal(notification); break; case 'REFUND': await handleRefund(notification); break; case 'EXPIRED': await handleExpiration(notification); break; } res.status(200).send(); });
Sign in with Apple REST API
Base URL:
https://appleid.apple.com
Endpoints
POST /auth/token # Exchange code for tokens POST /auth/revoke # Revoke tokens GET /auth/keys # Get JWKS for validation
Authentication Flow
- Client authenticates via Apple
- Server receives authorization code
- Exchange code for tokens at
/auth/token - Validate identity token
- Store refresh token securely
Generate Client Secret
function generateClientSecret(teamId, clientId, keyId, privateKey) { return jwt.sign({}, privateKey, { algorithm: 'ES256', expiresIn: '180d', // Max 6 months audience: 'https://appleid.apple.com', issuer: teamId, subject: clientId, header: { alg: 'ES256', kid: keyId } }); }
Token Exchange
async function exchangeAuthCode(code, clientSecret, clientId, redirectUri) { const params = new URLSearchParams({ client_id: clientId, client_secret: clientSecret, code: code, grant_type: 'authorization_code', redirect_uri: redirectUri }); const response = await fetch('https://appleid.apple.com/auth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params.toString() }); return response.json(); }
Token Response
{ "access_token": "...", "token_type": "Bearer", "expires_in": 3600, "refresh_token": "...", "id_token": "eyJraWQiOiJXNldjT0..." }
Validate Identity Token
const jwksClient = require('jwks-rsa'); const client = jwksClient({ jwksUri: 'https://appleid.apple.com/auth/keys' }); async function validateIdentityToken(idToken, audience) { const decoded = jwt.decode(idToken, { complete: true }); const key = await client.getSigningKey(decoded.header.kid); return jwt.verify(idToken, key.getPublicKey(), { algorithms: ['RS256'], issuer: 'https://appleid.apple.com', audience: audience }); }
Revoke Token (Required for Account Deletion)
async function revokeToken(token, clientSecret, clientId) { const params = new URLSearchParams({ client_id: clientId, client_secret: clientSecret, token: token, token_type_hint: 'refresh_token' }); await fetch('https://appleid.apple.com/auth/revoke', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params.toString() }); }
App Intents Framework
Framework for exposing app functionality to Siri and Shortcuts.
App Intent Example (Swift)
import AppIntents struct OrderCoffeeIntent: AppIntent { static var title: LocalizedStringResource = "Order Coffee" @Parameter(title: "Size") var size: CoffeeSize func perform() async throws -> some IntentResult { let order = try await CoffeeService.order(size: size) return .result(value: order.id) } } struct CoffeeShortcuts: AppShortcutsProvider { static var appShortcuts: [AppShortcut] { AppShortcut( intent: OrderCoffeeIntent(), phrases: ["Order coffee with \(.applicationName)"], shortTitle: "Order Coffee", systemImageName: "cup.and.saucer.fill" ) } }
Error Handling
Common Status Codes
| Code | Description |
|---|---|
| 401 | Invalid or expired JWT |
| 403 | Insufficient permissions |
| 404 | Resource not found |
| 429 | Rate limit exceeded |
| 500 | Apple server error |
Rate Limits
- App Store Connect API: ~1000 requests/hour per key
- App Store Server API: Varies by endpoint
- Sign in with Apple: ~20 requests/second
Retry Strategy
async function apiCallWithRetry(fn, maxRetries = 3) { for (let i = 0; i < maxRetries; i++) { try { return await fn(); } catch (error) { if (error.status === 429 && i < maxRetries - 1) { await new Promise(r => setTimeout(r, Math.pow(2, i) * 1000)); continue; } throw error; } } }
Security Best Practices
- Never expose private keys - Store in secure vault
- Validate all signed data - Always verify JWS signatures
- Use short-lived tokens - JWT should expire in 20 minutes
- Implement webhook verification - Validate all incoming notifications
- Store refresh tokens securely - Encrypt at rest
- Use environment-specific endpoints - Separate sandbox/production
- Regenerate client secrets - Sign in with Apple expires in 6 months
Environment Variables
# App Store Connect / Server API APP_STORE_ISSUER_ID=your-issuer-id APP_STORE_KEY_ID=your-key-id APP_STORE_PRIVATE_KEY_PATH=./AuthKey_XXXXX.p8 APP_BUNDLE_ID=com.yourcompany.yourapp APP_STORE_ENVIRONMENT=Sandbox # Sign in with Apple APPLE_TEAM_ID=your-team-id APPLE_CLIENT_ID=com.yourcompany.yourapp APPLE_KEY_ID=your-key-id APPLE_PRIVATE_KEY_PATH=./AuthKey_XXXXX.p8
Additional Resources
For detailed API specifications:
- See
- Complete endpoint referencereferences/app-store-connect-api.md - See
- Server API endpointsreferences/app-store-server-api.md - See
- Authentication flow detailsreferences/sign-in-with-apple.md - See
- Webhook event structuresreferences/server-notifications.md - See
- Ready-to-use code templatesassets/templates/