Manage in-app purchases and auto-renewable subscriptions via App Store Connect.
List in-app purchases for an app.
| Flag | Required | Description |
|---|---|---|
--app-id |
✓ | App ID |
--limit |
Max results | |
--output |
json (default) or table |
|
--pretty |
Pretty-print JSON |
asc iap list --app-id A123
asc iap list --app-id A123 --prettyJSON output:
{
"data": [
{
"affordances": {
"createLocalization": "asc iap-localizations create --iap-id iap-1 --locale en-US --name <name>",
"listLocalizations": "asc iap-localizations list --iap-id iap-1",
"listOfferCodes": "asc iap-offer-codes list --iap-id iap-1"
},
"appId": "A123",
"id": "iap-1",
"productId": "com.app.goldcoins",
"referenceName": "Gold Coins",
"state": "MISSING_METADATA",
"type": "CONSUMABLE"
}
]
}Create a new in-app purchase.
| Flag | Required | Description |
|---|---|---|
--app-id |
✓ | App ID |
--reference-name |
✓ | Internal name (not shown to users) |
--product-id |
✓ | Product ID (e.g. com.app.goldcoins) |
--type |
✓ | consumable, non-consumable, non-renewing-subscription |
asc iap create --app-id A123 --reference-name "Gold Coins" --product-id "com.app.goldcoins" --type consumableList localizations for an in-app purchase.
| Flag | Required | Description |
|---|---|---|
--iap-id |
✓ | IAP ID |
asc iap-localizations list --iap-id iap-1Create a per-locale name and description for an IAP.
| Flag | Required | Description |
|---|---|---|
--iap-id |
✓ | IAP ID |
--locale |
✓ | Locale code (e.g. en-US, zh-Hans) |
--name |
✓ | Display name shown to users |
--description |
Optional description |
asc iap-localizations create --iap-id iap-1 --locale en-US --name "Gold Coins" --description "In-game currency"
asc iap-localizations create --iap-id iap-1 --locale zh-Hans --name "金币"Submit an in-app purchase for App Store review.
| Flag | Required | Description |
|---|---|---|
--iap-id |
✓ | IAP ID |
asc iap submit --iap-id iap-1List available price points for an in-app purchase, optionally filtered by territory.
| Flag | Required | Description |
|---|---|---|
--iap-id |
✓ | IAP ID |
--territory |
Filter by territory code (e.g. USA) |
|
--output |
json (default) or table |
|
--pretty |
Pretty-print JSON |
asc iap price-points list --iap-id iap-1
asc iap price-points list --iap-id iap-1 --territory USA --prettyJSON output:
{
"data": [
{
"affordances": {
"listPricePoints": "asc iap price-points list --iap-id iap-1",
"setPrice": "asc iap prices set --iap-id iap-1 --base-territory USA --price-point-id pp-tier1"
},
"customerPrice": "0.99",
"iapId": "iap-1",
"id": "pp-tier1",
"proceeds": "0.70",
"territory": "USA"
}
]
}Set the price schedule for an in-app purchase (base territory + auto-pricing for all other territories).
| Flag | Required | Description |
|---|---|---|
--iap-id |
✓ | IAP ID |
--base-territory |
✓ | Base territory code (e.g. USA) |
--price-point-id |
✓ | Price point ID from asc iap price-points list |
PRICE_ID=$(asc iap price-points list --iap-id iap-1 --territory USA \
| jq -r '.data[] | select(.customerPrice == "0.99") | .id')
asc iap prices set --iap-id iap-1 --base-territory USA --price-point-id "$PRICE_ID"List subscription groups for an app.
| Flag | Required | Description |
|---|---|---|
--app-id |
✓ | App ID |
--limit |
Max results |
asc subscription-groups list --app-id A123Create a new subscription group.
| Flag | Required | Description |
|---|---|---|
--app-id |
✓ | App ID |
--reference-name |
✓ | Internal reference name for the group |
asc subscription-groups create --app-id A123 --reference-name "Premium Plans"List subscriptions in a group.
| Flag | Required | Description |
|---|---|---|
--group-id |
✓ | Subscription group ID |
--limit |
Max results |
asc subscriptions list --group-id grp-1Create a subscription tier within a group.
| Flag | Required | Description |
|---|---|---|
--group-id |
✓ | Subscription group ID |
--name |
✓ | Display name |
--product-id |
✓ | Product ID (e.g. com.app.monthly) |
--period |
✓ | ONE_WEEK, ONE_MONTH, TWO_MONTHS, THREE_MONTHS, SIX_MONTHS, ONE_YEAR |
--family-sharable |
Enable Family Sharing (flag) | |
--group-level |
Level for upgrade/downgrade ordering |
asc subscriptions create --group-id grp-1 --name "Monthly Premium" --product-id "com.app.monthly" --period ONE_MONTH
asc subscriptions create --group-id grp-1 --name "Annual Premium" --product-id "com.app.annual" --period ONE_YEAR --family-sharable --group-level 1List localizations for a subscription.
| Flag | Required | Description |
|---|---|---|
--subscription-id |
✓ | Subscription ID |
asc subscription-localizations list --subscription-id sub-1Create a per-locale name and description for a subscription.
| Flag | Required | Description |
|---|---|---|
--subscription-id |
✓ | Subscription ID |
--locale |
✓ | Locale code |
--name |
✓ | Display name in App Store |
--description |
Optional description |
asc subscription-localizations create --subscription-id sub-1 --locale en-US --name "Monthly Premium" --description "Full access to all features"Submit a subscription for App Store review.
| Flag | Required | Description |
|---|---|---|
--subscription-id |
✓ | Subscription ID |
--pretty |
Pretty-print JSON |
asc subscriptions submit --subscription-id sub-1The
submitaffordance appears inSubscriptionJSON only whenstate == READY_TO_SUBMIT.
List introductory offers for a subscription.
| Flag | Required | Description |
|---|---|---|
--subscription-id |
✓ | Subscription ID |
--output |
json (default) or table |
|
--pretty |
Pretty-print JSON |
asc subscription-offers list --subscription-id sub-1 --prettyCreate an introductory offer for a subscription.
| Flag | Required | Description |
|---|---|---|
--subscription-id |
✓ | Subscription ID |
--duration |
✓ | THREE_DAYS, ONE_WEEK, TWO_WEEKS, ONE_MONTH, TWO_MONTHS, THREE_MONTHS, SIX_MONTHS, ONE_YEAR |
--mode |
✓ | FREE_TRIAL, PAY_AS_YOU_GO, PAY_UP_FRONT |
--periods |
✓ | Number of periods (integer) |
--territory |
Territory code (e.g. USA) |
|
--price-point-id |
✓ for PAY_AS_YOU_GO / PAY_UP_FRONT | Subscription price point ID |
--start-date |
Start date YYYY-MM-DD |
|
--end-date |
End date YYYY-MM-DD |
|
--pretty |
Pretty-print JSON |
# Free trial — 1 month
asc subscription-offers create \
--subscription-id sub-1 \
--duration ONE_MONTH \
--mode FREE_TRIAL \
--periods 1 \
--pretty
# Discounted price — requires price point ID
asc subscription-offers create \
--subscription-id sub-1 \
--duration THREE_MONTHS \
--mode PAY_AS_YOU_GO \
--periods 3 \
--territory USA \
--price-point-id pp-123 \
--prettyList offer codes for a subscription.
| Flag | Required | Description |
|---|---|---|
--subscription-id |
✓ | Subscription ID |
--output |
json (default) or table |
|
--pretty |
Pretty-print JSON |
asc subscription-offer-codes list --subscription-id sub-1 --prettyCreate a new offer code for a subscription.
| Flag | Required | Description |
|---|---|---|
--subscription-id |
✓ | Subscription ID |
--name |
✓ | Offer code name (e.g. SUMMER2026) |
--duration |
✓ | THREE_DAYS, ONE_WEEK, TWO_WEEKS, ONE_MONTH, TWO_MONTHS, THREE_MONTHS, SIX_MONTHS, ONE_YEAR |
--mode |
✓ | FREE_TRIAL, PAY_AS_YOU_GO, PAY_UP_FRONT |
--periods |
✓ | Number of periods |
--eligibility |
✓ | Customer eligibility (repeatable): NEW, LAPSED, WIN_BACK, PAID_SUBSCRIBER |
--offer-eligibility |
✓ | STACKABLE, INTRODUCTORY, SUBSCRIPTION_OFFER |
asc subscription-offer-codes create \
--subscription-id sub-1 \
--name "SUMMER2026" \
--duration ONE_MONTH \
--mode FREE_TRIAL \
--periods 1 \
--eligibility NEW \
--eligibility LAPSED \
--offer-eligibility STACKABLEActivate or deactivate an offer code.
| Flag | Required | Description |
|---|---|---|
--offer-code-id |
✓ | Offer code ID |
--active |
✓ | true or false |
asc subscription-offer-codes update --offer-code-id oc-1 --active falseList custom redeemable codes for an offer code.
| Flag | Required | Description |
|---|---|---|
--offer-code-id |
✓ | Offer code ID |
asc subscription-offer-code-custom-codes list --offer-code-id oc-1Create a custom redeemable code for an offer code.
| Flag | Required | Description |
|---|---|---|
--offer-code-id |
✓ | Offer code ID |
--custom-code |
✓ | Custom code string (e.g. SUMMER2026) |
--number-of-codes |
✓ | Number of redemptions allowed |
--expiration-date |
Expiration date YYYY-MM-DD |
asc subscription-offer-code-custom-codes create \
--offer-code-id oc-1 \
--custom-code "SUMMER2026" \
--number-of-codes 1000 \
--expiration-date 2026-12-31Activate or deactivate a custom code.
| Flag | Required | Description |
|---|---|---|
--custom-code-id |
✓ | Custom code ID |
--active |
✓ | true or false |
asc subscription-offer-code-custom-codes update --custom-code-id cc-1 --active falseList one-time use code batches for an offer code.
| Flag | Required | Description |
|---|---|---|
--offer-code-id |
✓ | Offer code ID |
asc subscription-offer-code-one-time-codes list --offer-code-id oc-1Generate a batch of one-time use codes.
| Flag | Required | Description |
|---|---|---|
--offer-code-id |
✓ | Offer code ID |
--number-of-codes |
✓ | Number of codes to generate |
--expiration-date |
✓ | Expiration date YYYY-MM-DD |
asc subscription-offer-code-one-time-codes create \
--offer-code-id oc-1 \
--number-of-codes 5000 \
--expiration-date 2026-12-31Activate or deactivate a one-time code batch.
| Flag | Required | Description |
|---|---|---|
--one-time-code-id |
✓ | One-time code ID |
--active |
✓ | true or false |
asc subscription-offer-code-one-time-codes update --one-time-code-id otc-1 --active falseList offer codes for an in-app purchase.
| Flag | Required | Description |
|---|---|---|
--iap-id |
✓ | IAP ID |
asc iap-offer-codes list --iap-id iap-1Create a new offer code for an in-app purchase.
| Flag | Required | Description |
|---|---|---|
--iap-id |
✓ | IAP ID |
--name |
✓ | Offer code name |
--eligibility |
✓ | Customer eligibility (repeatable): NON_SPENDER, ACTIVE_SPENDER, CHURNED_SPENDER |
asc iap-offer-codes create \
--iap-id iap-1 \
--name "FREEGEMS" \
--eligibility NON_SPENDER \
--eligibility CHURNED_SPENDERActivate or deactivate an IAP offer code.
| Flag | Required | Description |
|---|---|---|
--offer-code-id |
✓ | Offer code ID |
--active |
✓ | true or false |
asc iap-offer-codes update --offer-code-id oc-1 --active falseSame interface as subscription custom codes — --offer-code-id, --custom-code, --number-of-codes, --expiration-date, --custom-code-id, --active.
Same interface as subscription one-time codes — --offer-code-id, --number-of-codes, --expiration-date, --one-time-code-id, --active.
APP_ID="A123456789"
# 1. Create a consumable IAP
IAP_ID=$(asc iap create \
--app-id "$APP_ID" \
--reference-name "Gold Coins" \
--product-id "com.app.goldcoins" \
--type consumable \
| jq -r '.data[0].id')
# 2. Add localizations
asc iap-localizations create --iap-id "$IAP_ID" --locale en-US --name "Gold Coins" --description "In-game currency"
asc iap-localizations create --iap-id "$IAP_ID" --locale zh-Hans --name "金币" --description "游戏货币"
# 3. Set pricing (Tier 1 in USA, Apple auto-calculates other territories)
PRICE_ID=$(asc iap price-points list --iap-id "$IAP_ID" --territory USA \
| jq -r '.data[] | select(.customerPrice == "0.99") | .id')
asc iap prices set --iap-id "$IAP_ID" --base-territory USA --price-point-id "$PRICE_ID"
# 4. Submit for review
asc iap submit --iap-id "$IAP_ID"
# 5. Create a subscription group
GROUP_ID=$(asc subscription-groups create \
--app-id "$APP_ID" \
--reference-name "Premium Plans" \
| jq -r '.data[0].id')
# 4. Create subscription tiers
MONTHLY_ID=$(asc subscriptions create \
--group-id "$GROUP_ID" \
--name "Monthly Premium" \
--product-id "com.app.monthly" \
--period ONE_MONTH \
--group-level 1 \
| jq -r '.data[0].id')
ANNUAL_ID=$(asc subscriptions create \
--group-id "$GROUP_ID" \
--name "Annual Premium" \
--product-id "com.app.annual" \
--period ONE_YEAR \
--family-sharable \
--group-level 2 \
| jq -r '.data[0].id')
# 5. Add subscription localizations
asc subscription-localizations create --subscription-id "$MONTHLY_ID" --locale en-US --name "Monthly Premium" --description "Full access, billed monthly"
asc subscription-localizations create --subscription-id "$ANNUAL_ID" --locale en-US --name "Annual Premium" --description "Full access, billed annually — save 30%"ASCCommand Infrastructure Domain
────────────────────────────────────────────────────────────────────────────
IAPList / IAPCreate SDKInAppPurchaseRepository InAppPurchase
IAPSubmit SDKInAppPurchaseSubmissionRepository InAppPurchaseSubmission
IAPPricePointsList SDKInAppPurchasePriceRepository InAppPurchasePricePoint
IAPPricesSet SDKInAppPurchasePriceRepository InAppPurchasePriceSchedule
IAPLocalizationsList/Create SDKIAPLocalizationRepo InAppPurchaseLocalization
SubscriptionGroupsList/Create SDKSubscriptionGroupRepo SubscriptionGroup
SubscriptionsList/Create SDKSubscriptionRepository Subscription
SubLocalizationsList/Create SDKSubLocalizationRepo SubscriptionLocalization
SubOfferCodesList/Create/Update SDKSubscriptionOfferCodeRepo SubscriptionOfferCode
SubOfferCodeCustomCodesList/ SDKSubscriptionOfferCodeRepo SubscriptionOfferCodeCustomCode
Create/Update
SubOfferCodeOneTimeCodesList/ SDKSubscriptionOfferCodeRepo SubscriptionOfferCodeOneTimeUseCode
Create/Update
IAPOfferCodesList/Create/Update SDKIAPOfferCodeRepo InAppPurchaseOfferCode
IAPOfferCodeCustomCodesList/ SDKIAPOfferCodeRepo InAppPurchaseOfferCodeCustomCode
Create/Update
IAPOfferCodeOneTimeCodesList/ SDKIAPOfferCodeRepo InAppPurchaseOfferCodeOneTimeUseCode
Create/Update
All Infrastructure repositories inject the parent ID from the request parameter since the ASC API does not return it in responses.
API versions used:
- List IAPs:
GET /v1/apps/{id}/inAppPurchasesV2(SDK v1) - Create IAP:
POST /v2/inAppPurchases(SDK v2) - List IAP localizations:
GET /v2/inAppPurchases/{id}/inAppPurchaseLocalizations(SDK v2) - Create IAP localization:
POST /v1/inAppPurchaseLocalizations(SDK v1) - Subscription groups:
GET/POST /v1/subscriptionGroups(SDK v1) - Subscriptions:
GET /v1/subscriptionGroups/{id}/subscriptions,POST /v1/subscriptions(SDK v1) - Subscription localizations:
GET /v1/subscriptions/{id}/subscriptionLocalizations,POST /v1/subscriptionLocalizations(SDK v1)
public struct InAppPurchase: Sendable, Codable, Equatable, Identifiable {
public let id: String
public let appId: String // parent — injected by Infrastructure
public let referenceName: String
public let productId: String
public let type: InAppPurchaseType
public let state: InAppPurchaseState
}InAppPurchaseType raw values: CONSUMABLE, NON_CONSUMABLE, NON_RENEWING_SUBSCRIPTION
CLI arguments: consumable, non-consumable, non-renewing-subscription
InAppPurchaseState semantic booleans:
isApproved/isLive—truewhenAPPROVEDisEditable—truewhenMISSING_METADATA,REJECTED, orDEVELOPER_ACTION_NEEDEDisPendingReview—truewhenWAITING_FOR_REVIEWorIN_REVIEW
Affordances: listLocalizations, createLocalization, listOfferCodes, listPricePoints (always); submit only when state == .readyToSubmit
public struct InAppPurchaseSubmission: Sendable, Equatable, Identifiable, Codable {
public let id: String
public let iapId: String // parent — injected by Infrastructure
}Affordances: listLocalizations
public struct InAppPurchasePricePoint: Sendable, Equatable, Identifiable {
public let id: String
public let iapId: String // parent — injected by Infrastructure
public let territory: String? // omitted from JSON when nil
public let customerPrice: String? // omitted from JSON when nil
public let proceeds: String? // omitted from JSON when nil
}Affordances: listPricePoints (always); setPrice only when territory != nil
public struct InAppPurchasePriceSchedule: Sendable, Equatable, Identifiable, Codable {
public let id: String
public let iapId: String // parent — injected by Infrastructure
}Affordances: listPricePoints
public struct InAppPurchaseLocalization: Sendable, Equatable, Identifiable {
public let id: String
public let iapId: String // parent — injected by Infrastructure
public let locale: String
public let name: String?
public let description: String?
public let state: InAppPurchaseLocalizationState?
}Nil fields are omitted from JSON (custom Codable with encodeIfPresent).
Affordances: listSiblings
public struct SubscriptionGroup: Sendable, Codable, Equatable, Identifiable {
public let id: String
public let appId: String // parent — injected by Infrastructure
public let referenceName: String
}Affordances: listSubscriptions, createSubscription
public struct Subscription: Sendable, Equatable, Identifiable {
public let id: String
public let groupId: String // parent — injected by Infrastructure
public let name: String
public let productId: String
public let subscriptionPeriod: SubscriptionPeriod
public let isFamilySharable: Bool
public let state: SubscriptionState
public let groupLevel: Int? // omitted from JSON when nil
}SubscriptionPeriod values: ONE_WEEK, ONE_MONTH, TWO_MONTHS, THREE_MONTHS, SIX_MONTHS, ONE_YEAR
SubscriptionState semantic booleans: same as InAppPurchaseState
Affordances: createIntroductoryOffer, createLocalization, listIntroductoryOffers, listLocalizations, listOfferCodes (always); submit only when state == READY_TO_SUBMIT
public struct SubscriptionSubmission: Sendable, Equatable, Identifiable, Codable {
public let id: String
public let subscriptionId: String // parent — injected by Infrastructure
}Affordances: listLocalizations
public struct SubscriptionIntroductoryOffer: Sendable, Equatable, Identifiable {
public let id: String
public let subscriptionId: String // parent — injected by Infrastructure
public let duration: SubscriptionOfferDuration
public let offerMode: SubscriptionOfferMode
public let numberOfPeriods: Int
public let startDate: String? // omitted from JSON when nil
public let endDate: String? // omitted from JSON when nil
public let territory: String? // omitted from JSON when nil
}SubscriptionOfferDuration values: THREE_DAYS, ONE_WEEK, TWO_WEEKS, ONE_MONTH, TWO_MONTHS, THREE_MONTHS, SIX_MONTHS, ONE_YEAR
SubscriptionOfferMode values: PAY_AS_YOU_GO, PAY_UP_FRONT, FREE_TRIAL
requiresPricePoint—trueforPAY_AS_YOU_GOandPAY_UP_FRONT; a--price-point-idis required when creating those offers
Nil fields are omitted from JSON (custom Codable with encodeIfPresent).
Affordances: listOffers
public struct SubscriptionLocalization: Sendable, Equatable, Identifiable {
public let id: String
public let subscriptionId: String // parent — injected by Infrastructure
public let locale: String
public let name: String?
public let description: String?
public let state: SubscriptionLocalizationState?
}Nil fields are omitted from JSON.
Affordances: listSiblings
Sources/Domain/Apps/
├── InAppPurchases/
│ ├── InAppPurchase.swift
│ ├── InAppPurchaseRepository.swift
│ ├── InAppPurchaseSubmission.swift
│ ├── InAppPurchaseSubmissionRepository.swift
│ ├── InAppPurchasePricePoint.swift
│ ├── InAppPurchasePriceSchedule.swift
│ ├── InAppPurchasePriceRepository.swift
│ ├── Localizations/
│ │ ├── InAppPurchaseLocalization.swift
│ │ └── InAppPurchaseLocalizationRepository.swift
│ └── OfferCodes/
│ ├── InAppPurchaseOfferCode.swift
│ ├── InAppPurchaseOfferCodeCustomCode.swift
│ ├── InAppPurchaseOfferCodeOneTimeUseCode.swift
│ └── InAppPurchaseOfferCodeRepository.swift
└── Subscriptions/
├── SubscriptionGroup.swift
├── SubscriptionGroupRepository.swift
├── Subscription.swift
├── SubscriptionRepository.swift
├── SubscriptionSubmission.swift
├── SubscriptionSubmissionRepository.swift
├── IntroductoryOffers/
│ ├── SubscriptionIntroductoryOffer.swift
│ └── SubscriptionIntroductoryOfferRepository.swift
├── OfferCodes/
│ ├── SubscriptionOfferCode.swift
│ ├── SubscriptionOfferCodeCustomCode.swift
│ ├── SubscriptionOfferCodeOneTimeUseCode.swift
│ └── SubscriptionOfferCodeRepository.swift
└── Localizations/
├── SubscriptionLocalization.swift
└── SubscriptionLocalizationRepository.swift
Sources/Infrastructure/Apps/
├── InAppPurchases/
│ ├── SDKInAppPurchaseRepository.swift
│ ├── SDKInAppPurchaseSubmissionRepository.swift
│ ├── SDKInAppPurchasePriceRepository.swift
│ ├── Localizations/
│ │ └── SDKInAppPurchaseLocalizationRepository.swift
│ └── OfferCodes/
│ └── SDKInAppPurchaseOfferCodeRepository.swift
└── Subscriptions/
├── SDKSubscriptionGroupRepository.swift
├── SDKSubscriptionRepository.swift
├── SDKSubscriptionSubmissionRepository.swift
├── IntroductoryOffers/
│ └── SDKSubscriptionIntroductoryOfferRepository.swift
├── OfferCodes/
│ └── SDKSubscriptionOfferCodeRepository.swift
└── Localizations/
└── SDKSubscriptionLocalizationRepository.swift
Sources/ASCCommand/Commands/
├── IAP/
│ ├── IAPCommand.swift
│ ├── IAPList.swift
│ ├── IAPCreate.swift
│ ├── IAPSubmit.swift
│ ├── IAPPricePointsCommand.swift
│ ├── IAPPricePointsList.swift
│ ├── IAPPricesCommand.swift
│ └── IAPPricesSet.swift
├── IAPLocalizations/
│ ├── IAPLocalizationsCommand.swift
│ ├── IAPLocalizationsList.swift
│ └── IAPLocalizationsCreate.swift
├── SubscriptionGroups/
│ ├── SubscriptionGroupsCommand.swift
│ ├── SubscriptionGroupsList.swift
│ └── SubscriptionGroupsCreate.swift
├── Subscriptions/
│ ├── SubscriptionsCommand.swift
│ ├── SubscriptionsList.swift
│ ├── SubscriptionsCreate.swift
│ └── SubscriptionsSubmit.swift
├── SubscriptionLocalizations/
│ ├── SubscriptionLocalizationsCommand.swift
│ ├── SubscriptionLocalizationsList.swift
│ └── SubscriptionLocalizationsCreate.swift
├── SubscriptionOffers/
│ ├── SubscriptionOffersCommand.swift
│ ├── SubscriptionOffersList.swift
│ └── SubscriptionOffersCreate.swift
├── SubscriptionOfferCodes/
│ ├── SubscriptionOfferCodesCommand.swift
│ ├── SubscriptionOfferCodesList.swift
│ ├── SubscriptionOfferCodesCreate.swift
│ └── SubscriptionOfferCodesUpdate.swift
├── SubscriptionOfferCodeCustomCodes/
│ ├── SubscriptionOfferCodeCustomCodesCommand.swift
│ ├── SubscriptionOfferCodeCustomCodesList.swift
│ ├── SubscriptionOfferCodeCustomCodesCreate.swift
│ └── SubscriptionOfferCodeCustomCodesUpdate.swift
├── SubscriptionOfferCodeOneTimeCodes/
│ ├── SubscriptionOfferCodeOneTimeCodesCommand.swift
│ ├── SubscriptionOfferCodeOneTimeCodesList.swift
│ ├── SubscriptionOfferCodeOneTimeCodesCreate.swift
│ └── SubscriptionOfferCodeOneTimeCodesUpdate.swift
├── IAPOfferCodes/
│ ├── IAPOfferCodesCommand.swift
│ ├── IAPOfferCodesList.swift
│ ├── IAPOfferCodesCreate.swift
│ └── IAPOfferCodesUpdate.swift
├── IAPOfferCodeCustomCodes/
│ ├── IAPOfferCodeCustomCodesCommand.swift
│ ├── IAPOfferCodeCustomCodesList.swift
│ ├── IAPOfferCodeCustomCodesCreate.swift
│ └── IAPOfferCodeCustomCodesUpdate.swift
└── IAPOfferCodeOneTimeCodes/
├── IAPOfferCodeOneTimeCodesCommand.swift
├── IAPOfferCodeOneTimeCodesList.swift
├── IAPOfferCodeOneTimeCodesCreate.swift
└── IAPOfferCodeOneTimeCodesUpdate.swift
Wiring files:
| File | Change |
|---|---|
Sources/ASCCommand/ASC.swift |
Register 13 command groups (7 existing + 6 new offer code groups) |
Sources/ASCCommand/ClientProvider.swift |
10 factory methods (8 existing + 2 new offer code repos) |
Sources/Infrastructure/Client/ClientFactory.swift |
10 factory methods (8 existing + 2 new offer code repos) |
| Command | SDK Endpoint | SDK Version |
|---|---|---|
iap list |
APIEndpoint.v1.apps.id(appId).inAppPurchasesV2.get() |
v1 |
iap create |
APIEndpoint.v2.inAppPurchases.post(InAppPurchaseV2CreateRequest) |
v2 |
iap submit |
APIEndpoint.v1.inAppPurchaseSubmissions.post(InAppPurchaseSubmissionCreateRequest) |
v1 |
iap price-points list |
APIEndpoint.v2.inAppPurchases.id(iapId).pricePoints.get() |
v2 |
iap prices set |
APIEndpoint.v1.inAppPurchasePriceSchedules.post(InAppPurchasePriceScheduleCreateRequest) |
v1 |
iap-localizations list |
APIEndpoint.v2.inAppPurchases.id(iapId).inAppPurchaseLocalizations.get() |
v2 |
iap-localizations create |
APIEndpoint.v1.inAppPurchaseLocalizations.post(InAppPurchaseLocalizationCreateRequest) |
v1 |
subscription-groups list |
APIEndpoint.v1.apps.id(appId).subscriptionGroups.get() |
v1 |
subscription-groups create |
APIEndpoint.v1.subscriptionGroups.post(SubscriptionGroupCreateRequest) |
v1 |
subscriptions list |
APIEndpoint.v1.subscriptionGroups.id(groupId).subscriptions.get() |
v1 |
subscriptions create |
APIEndpoint.v1.subscriptions.post(SubscriptionCreateRequest) |
v1 |
subscription-localizations list |
APIEndpoint.v1.subscriptions.id(subscriptionId).subscriptionLocalizations.get() |
v1 |
subscription-localizations create |
APIEndpoint.v1.subscriptionLocalizations.post(SubscriptionLocalizationCreateRequest) |
v1 |
subscriptions submit |
APIEndpoint.v1.subscriptionSubmissions.post(SubscriptionSubmissionCreateRequest) |
v1 |
subscription-offers list |
APIEndpoint.v1.subscriptions.id(subscriptionId).introductoryOffers.get() |
v1 |
subscription-offers create |
APIEndpoint.v1.subscriptionIntroductoryOffers.post(SubscriptionIntroductoryOfferCreateRequest) |
v1 |
subscription-offer-codes list |
APIEndpoint.v1.subscriptions.id(subscriptionId).offerCodes.get() |
v1 |
subscription-offer-codes create |
APIEndpoint.v1.subscriptionOfferCodes.post(SubscriptionOfferCodeCreateRequest) |
v1 |
subscription-offer-codes update |
APIEndpoint.v1.subscriptionOfferCodes.id(offerCodeId).patch(SubscriptionOfferCodeUpdateRequest) |
v1 |
subscription-offer-code-custom-codes list |
APIEndpoint.v1.subscriptionOfferCodes.id(offerCodeId).customCodes.get() |
v1 |
subscription-offer-code-custom-codes create |
APIEndpoint.v1.subscriptionOfferCodeCustomCodes.post(SubscriptionOfferCodeCustomCodeCreateRequest) |
v1 |
subscription-offer-code-one-time-codes list |
APIEndpoint.v1.subscriptionOfferCodes.id(offerCodeId).oneTimeUseCodes.get() |
v1 |
subscription-offer-code-one-time-codes create |
APIEndpoint.v1.subscriptionOfferCodeOneTimeUseCodes.post(SubscriptionOfferCodeOneTimeUseCodeCreateRequest) |
v1 |
iap-offer-codes list |
APIEndpoint.v2.inAppPurchases.id(iapId).offerCodes.get() |
v2 |
iap-offer-codes create |
APIEndpoint.v1.inAppPurchaseOfferCodes.post(InAppPurchaseOfferCodeCreateRequest) |
v1 |
iap-offer-codes update |
APIEndpoint.v1.inAppPurchaseOfferCodes.id(offerCodeId).patch(InAppPurchaseOfferCodeUpdateRequest) |
v1 |
iap-offer-code-custom-codes list |
APIEndpoint.v1.inAppPurchaseOfferCodes.id(offerCodeId).customCodes.get() |
v1 |
iap-offer-code-custom-codes create |
APIEndpoint.v1.inAppPurchaseOfferCodeCustomCodes.post(InAppPurchaseOfferCodeCustomCodeCreateRequest) |
v1 |
iap-offer-code-one-time-codes list |
APIEndpoint.v1.inAppPurchaseOfferCodes.id(offerCodeId).oneTimeUseCodes.get() |
v1 |
iap-offer-code-one-time-codes create |
APIEndpoint.v1.inAppPurchaseOfferCodeOneTimeUseCodes.post(InAppPurchaseOfferCodeOneTimeUseCodeCreateRequest) |
v1 |
// Domain test
@Test func `subscription group affordances include listSubscriptions`() {
let group = MockRepositoryFactory.makeSubscriptionGroup(id: "grp-1")
#expect(group.affordances["listSubscriptions"] == "asc subscriptions list --group-id grp-1")
}
// Command test
@Test func `listed subscriptions include groupId, period, state and affordances`() async throws {
let mockRepo = MockSubscriptionRepository()
given(mockRepo).listSubscriptions(groupId: .any, limit: .any)
.willReturn(PaginatedResponse(data: [
Subscription(id: "sub-1", groupId: "grp-1", name: "Monthly Premium",
productId: "com.app.monthly", subscriptionPeriod: .oneMonth,
isFamilySharable: false, state: .missingMetadata)
], nextCursor: nil))
let cmd = try SubscriptionsList.parse(["--group-id", "grp-1", "--pretty"])
let output = try await cmd.execute(repo: mockRepo)
#expect(output == """
{
"data" : [
{
"affordances" : { ... },
"groupId" : "grp-1",
"id" : "sub-1",
...
}
]
}
""")
}swift test --filter 'IAPListTests|IAPCreateTests|IAPSubmitTests|IAPPricePointsListTests|IAPPricesSetTests|SubscriptionGroupsListTests|SubscriptionsListTests'public struct SubscriptionOfferCode: Sendable, Equatable, Identifiable {
public let id: String
public let subscriptionId: String // parent — injected by Infrastructure
public let name: String
public let customerEligibilities: [SubscriptionCustomerEligibility]
public let offerEligibility: SubscriptionOfferEligibility
public let duration: SubscriptionOfferDuration
public let offerMode: SubscriptionOfferMode
public let numberOfPeriods: Int
public let totalNumberOfCodes: Int? // omitted from JSON when nil
public let isActive: Bool
}SubscriptionCustomerEligibility: NEW, LAPSED, WIN_BACK, PAID_SUBSCRIBER
SubscriptionOfferEligibility: STACKABLE, INTRODUCTORY, SUBSCRIPTION_OFFER
Affordances: listOfferCodes, listCustomCodes, listOneTimeCodes (always); deactivate only when isActive == true
public struct SubscriptionOfferCodeCustomCode: Sendable, Equatable, Identifiable {
public let id: String
public let offerCodeId: String // parent — injected by Infrastructure
public let customCode: String
public let numberOfCodes: Int
public let createdDate: String? // omitted from JSON when nil
public let expirationDate: String? // omitted from JSON when nil
public let isActive: Bool
}Affordances: listCustomCodes (always); deactivate only when isActive == true
public struct SubscriptionOfferCodeOneTimeUseCode: Sendable, Equatable, Identifiable {
public let id: String
public let offerCodeId: String // parent — injected by Infrastructure
public let numberOfCodes: Int
public let createdDate: String? // omitted from JSON when nil
public let expirationDate: String? // omitted from JSON when nil
public let isActive: Bool
}Affordances: listOneTimeCodes (always); deactivate only when isActive == true
public struct InAppPurchaseOfferCode: Sendable, Equatable, Identifiable {
public let id: String
public let iapId: String // parent — injected by Infrastructure
public let name: String
public let customerEligibilities: [IAPCustomerEligibility]
public let isActive: Bool
public let totalNumberOfCodes: Int? // omitted from JSON when nil
}IAPCustomerEligibility: NON_SPENDER, ACTIVE_SPENDER, CHURNED_SPENDER
Affordances: listOfferCodes, listCustomCodes, listOneTimeCodes (always); deactivate only when isActive == true
Same structure as subscription counterparts with offerCodeId as parent.
Natural next steps:
Subscription Promotional Offers — POST /v1/subscriptionPromotionalOffers:
asc subscription-promotional-offers create --subscription-id <id> --name "Winback" --duration ONE_MONTH --mode PAY_AS_YOU_GO