iap.yaml reference
One iap.yaml describes the whole billing catalog for a project. Each provider
reads only the fields it needs. Copy iap.example.yaml and edit the
identifiers.
Top level
Section titled “Top level”| Field | Type | Notes |
|---|---|---|
appName |
string | Display name used in reports / default group naming. |
bundleId |
string | App Store bundle id. |
packageName |
string | Google Play package name. |
entitlement |
object | The single “what you unlock” key shared across stores. |
pricing |
object | The single territory/region/currency provisioned. |
products |
list | The purchasable items. |
revenuecat |
object | RevenueCat-specific knobs (optional). |
appstore |
object | App Store-specific knobs. |
screenshotsDir |
string | Base dir for relative reviewScreenshot paths (optional; default: CWD). |
entitlement
Section titled “entitlement”| Field | Notes |
|---|---|
key |
e.g. pro — the entitlement identifier your app checks. Required. |
displayName |
e.g. Pro access. |
pricing
Section titled “pricing”storectl provisions a single territory/region/currency (multi-region is a deliberate non-goal of the first version).
| Field | Example | Notes |
|---|---|---|
territory |
JPN |
App Store territory code (ISO-3166 alpha-3). |
region |
JP |
Google Play region code (ISO-3166 alpha-2). |
currency |
JPY |
ISO-4217. Required. |
products[]
Section titled “products[]”Each product is either an auto-renewable subscription (default) or a
non-consumable one-time purchase (type: nonConsumable). Subscriptions use
period / playBasePlanId; non-consumables ignore them.
| Field | Applies to | Notes |
|---|---|---|
key |
all | Logical name, e.g. monthly. Required, unique. |
type |
all | subscription (default) or nonConsumable. |
appStoreId |
all | App Store Connect productId. |
playProductId |
all | Play subscription / managed-product id. |
playBasePlanId |
subscriptions | Play base plan id. |
period |
subscriptions | ISO-8601: P1W P1M P2M P3M P6M P1Y. Required for subscriptions. |
price |
all | Major-currency units; decimals OK (4.99 = $4.99, 600 = ¥600). Must be > 0. |
rcPackage |
all | RevenueCat package key, e.g. $rc_monthly. |
localizations |
all | Per-locale store listing (see below). |
reviewScreenshot |
all | App Store only. Path to the IAP/subscription review screenshot (one per product, required before submission). Placed, not generated; resolved via screenshotsDir. Idempotent on the stored MD5. |
intro |
subscriptions | Introductory offer (free trial / intro price). See below. |
intro (subscriptions)
Section titled “intro (subscriptions)”A free trial or introductory price, applied on both stores (App Store
subscriptionIntroductoryOffers in the configured territory; Google Play a
base-plan offer in the configured region). RevenueCat reads it from the stores
automatically. Idempotent on the offer shape; plan reports create/drift/exists.
| Field | Notes |
|---|---|
mode |
freeTrial / payAsYouGo / payUpFront. |
period |
Offer duration: P3D P1W P2W P1M P2M P3M P6M P1Y. |
cycles |
payAsYouGo only — number of billing periods (default 1). |
price |
Paid modes only; configured-currency major units. Omit for freeTrial. |
intro: { mode: freeTrial, period: P1W }# or an intro price:intro: { mode: payAsYouGo, period: P1M, cycles: 3, price: 300 }localizations[]
Section titled “localizations[]”| Field | Notes |
|---|---|
locale |
BCP-47, e.g. ja, en-US. Play maps ja → ja-JP automatically. |
name |
Display name / title. App Store IAP limit: ≤ 30 chars. |
description |
Optional. App Store IAP limit: ≤ 45 chars. |
revenuecat (optional)
Section titled “revenuecat (optional)”Skipped entirely if REVENUECAT_API_KEY is unset.
| Field | Notes |
|---|---|
project |
Project name, used to resolve project_id (or set REVENUECAT_PROJECT_ID). |
offering |
Offering lookup_key, e.g. default. |
appstore
Section titled “appstore”| Field | Notes |
|---|---|
subscriptionGroup |
Subscription group referenceName, e.g. Sonir Pro. |
Full example
Section titled “Full example”A complete iap.yaml: two auto-renewable subscriptions and one non-consumable,
across four locales. Copy it and swap in your identifiers.
# iapctl desired-state. One file drives App Store Connect, Google Play, RevenueCat.appName: SonirbundleId: app.sonir.app # App Store bundle id (= PRODUCT_BUNDLE_IDENTIFIER)packageName: app.sonir.app # Google Play package name (= applicationId)screenshotsDir: ./review-shots # base dir for reviewScreenshot paths (App Store only)
entitlement: key: pro # the entitlement identifier your app checks displayName: Pro access
# A single territory/region/currency is provisioned (multi-region is a non-goal).pricing: territory: JPN # App Store territory (ISO-3166 alpha-3) region: JP # Google Play region (ISO-3166 alpha-2) currency: JPY # ISO-4217
products: # ── Auto-renewable subscription (default type) ────────────────────────────── - key: monthly appStoreId: sonir.pro.monthly # ASC productId playProductId: sonir_pro_monthly # Play subscription id (lowercase/digits/_/.) playBasePlanId: monthly # Play base plan id (lowercase/hyphens) period: P1M # ISO-8601: P1W P1M P2M P3M P6M P1Y price: 500 # major units, decimals OK (¥500; 4.99 = $4.99) rcPackage: $rc_monthly # RevenueCat package key reviewScreenshot: monthly/review.png # App Store review screenshot (one per product) # App Store IAP limits: name <=30, description <=45. localizations: - { locale: ja, name: "Sonir Pro(月額)", description: "全機能を月額で。" } - { locale: en-US, name: "Sonir Pro (Monthly)", description: "All features, billed monthly." } - { locale: de-DE, name: "Sonir Pro (Monatlich)", description: "Alle Funktionen, monatlich." } - { locale: fr-FR, name: "Sonir Pro (mensuel)", description: "Toutes les fonctions, par mois." }
- key: yearly appStoreId: sonir.pro.yearly playProductId: sonir_pro_yearly playBasePlanId: yearly period: P1Y price: 5400 rcPackage: $rc_annual intro: { mode: freeTrial, period: P1W } # 1-week free trial (both stores) localizations: - { locale: ja, name: "Sonir Pro(年額)", description: "全機能を年額で。3か月分お得。" } - { locale: en-US, name: "Sonir Pro (Yearly)", description: "All features, billed yearly." } - { locale: de-DE, name: "Sonir Pro (Jährlich)", description: "Alle Funktionen, jährlich." } - { locale: fr-FR, name: "Sonir Pro (annuel)", description: "Toutes les fonctions, par an." }
# ── Non-consumable (one-time). type: nonConsumable drops period/playBasePlanId. - key: lifetime type: nonConsumable appStoreId: sonir.pro.lifetime playProductId: sonir_pro_lifetime price: 11000 rcPackage: $rc_lifetime localizations: - { locale: ja, name: "Sonir Pro(買い切り)", description: "全機能を一度の購入で永続解放。" } - { locale: en-US, name: "Sonir Pro (Lifetime)", description: "Unlock all features with one purchase." } - { locale: de-DE, name: "Sonir Pro (Einmalig)", description: "Alle Funktionen mit einem Kauf." } - { locale: fr-FR, name: "Sonir Pro (à vie)", description: "Toutes les fonctions en un seul achat." }
revenuecat: project: Sonir # project NAME → resolves project_id (or set REVENUECAT_PROJECT_ID) offering: default # offering lookup_key
appstore: subscriptionGroup: Sonir Pro # subscription group referenceNameWhat storectl does not do
Section titled “What storectl does not do”By design (one-time human setup): account creation, Paid Apps Agreement / banking / tax, RevenueCat app registration, build upload / Submit-for-Review, and pricing beyond the single configured territory.