Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/tomevault-io/skills-registry --skill apple-developer-apis명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
| Use when this capability is needed.
> Use when this capability is needed.
Review architecture and API design for the vfs-s3 project. Use when the user mentions @architect, asks to review an issue's design, discuss module boundaries, API shape, or architectural decisions for vfs-s3. Also trigger when the user wants to create an ADR (Architecture Decision Record) or evaluate a technical approach for the project. Intended for dispatch from Codex automation or Claude routines; GitHub trigger phrase: @vfs-s3-bot please prepare design doc Use when this capability is needed.
SOC 직업 분류 기준
SKILL.md 표시 중
| name | apple-developer-apis |
| description | >- Use when this capability is needed. |
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.
Apple provides several REST APIs for managing apps, subscriptions, and authentication:
All APIs use JWT (JSON Web Token) authentication with ES256 algorithm.
.p8 private key file (only downloadable once)// 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
}
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' }
});
}
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"})
Base URL: https://api.appstoreconnect.apple.com/v1
REST API for automating App Store Connect tasks.
GET /v1/apps # List all apps
GET /v1/apps/{id} # Get specific app
GET /v1/apps/{id}/builds # List builds for app
GET /v1/builds # List all builds
POST /v1/betaTesters # Add beta tester
GET /v1/betaGroups # List beta groups
GET /v1/certificates # List certificates
POST /v1/certificates # Create certificate
GET /v1/profiles # List provisioning profiles
GET /v1/apps/{id}/inAppPurchases # List IAPs
POST /v1/inAppPurchases # Create IAP
GET /v1/subscriptionGroups # List subscription groups
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();
}
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.
GET /inApps/v1/history/{transactionId}
Returns complete purchase history for a customer.
Query Parameters:
revision - Pagination tokensort - ASCENDING or DESCENDINGproductType - AUTO_RENEWABLE, NON_RENEWABLE, CONSUMABLE, NON_CONSUMABLEGET /inApps/v1/subscriptions/{transactionId}
Returns current status of all subscriptions.
GET /inApps/v2/refund/lookup/{transactionId}
POST /inApps/v1/lookup/{orderId}
PUT /inApps/v1/transactions/consumption/{transactionId}
Send consumption data for consumable IAPs.
POST /inApps/v1/notifications/test
All responses contain signed JWS data:
{
"signedTransactions": ["eyJhbGciOiJFUzI1NiIsIng1YyI6Wy..."],
"revision": "next_page_token"
}
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);
Webhooks for subscription lifecycle events.
| Type | Description |
|---|---|
SUBSCRIBED | Initial subscription purchase |
DID_RENEW | Subscription renewed |
DID_FAIL_TO_RENEW | Renewal failed |
EXPIRED | Subscription expired |
REFUND | Transaction refunded |
DID_CHANGE_RENEWAL_STATUS | Auto-renew toggled |
DID_CHANGE_RENEWAL_PREF | Plan changed |
GRACE_PERIOD_EXPIRED | Billing grace period ended |
OFFER_REDEEMED | Promotional offer applied |
CONSUMPTION_REQUEST | Apple requests consumption data |
REVOKE | Family sharing revoked |
INITIAL_BUY / RESUBSCRIBEDOWNGRADE / UPGRADEAUTO_RENEW_ENABLED / AUTO_RENEW_DISABLEDVOLUNTARY / BILLING_RETRY / PRICE_INCREASE{
"signedPayload": "eyJhbGciOiJFUzI1NiIsIng1YyI6Wy..."
}
{
"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
}
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();
});
Base URL: https://appleid.apple.com
POST /auth/token # Exchange code for tokens
POST /auth/revoke # Revoke tokens
GET /auth/keys # Get JWKS for validation
/auth/tokenfunction 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 }
});
}
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();
}
{
"access_token": "...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "...",
"id_token": "eyJraWQiOiJXNldjT0..."
}
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
});
}
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()
});
}
Framework for exposing app functionality to Siri and Shortcuts.
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"
)
}
}
| Code | Description |
|---|---|
| 401 | Invalid or expired JWT |
| 403 | Insufficient permissions |
| 404 | Resource not found |
| 429 | Rate limit exceeded |
| 500 | Apple server error |
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;
}
}
}
# 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
For detailed API specifications:
references/app-store-connect-api.md - Complete endpoint referencereferences/app-store-server-api.md - Server API endpointsreferences/sign-in-with-apple.md - Authentication flow detailsreferences/server-notifications.md - Webhook event structuresassets/templates/ - Ready-to-use code templatesConverted and distributed by TomeVault — claim your Tome and manage your conversions.