Skip to content

Getting started

storectl is two CLIs that share auth and an HTTP client. A typical rollout goes metadata first, then billing:

  1. metactl — App Review text metadata (name, descriptions, keywords, review contact, age rating) from metadata.yaml.
  2. iapctl — the billing catalog (subscriptions + one-time products, entitlements, offerings) from iap.yaml.

Both use the same plan / apply model and are idempotent — safe to re-run.

Terminal window
curl -fsSL https://storectl.dev/install.sh | sh
metactl version && iapctl version

The installer detects your OS/arch, downloads the signed archive, verifies the checksum (and the cosign signature when cosign is present), and installs both metactl and iapctl. Pin a version with STORECTL_VERSION, change the target with STORECTL_INSTALL_DIR, or require signature verification with STORECTL_VERIFY=1.

A provider whose variables are unset is skipped (reported as skip, not an error), so you can run only the stores you have keys for.

Provider Variables
App Store Connect ASC_KEY_ID, ASC_ISSUER_ID, and ASC_PRIVATE_KEY (the .p8 contents) or ASC_PRIVATE_KEY_PATH
Google Play GOOGLE_PLAY_SERVICE_ACCOUNT_JSON (inline) or GOOGLE_PLAY_SERVICE_ACCOUNT_PATH / GOOGLE_APPLICATION_CREDENTIALS
RevenueCat (optional, iapctl only) REVENUECAT_API_KEY (a sk_… V2 secret)

Set up the store listing and review info first, from metadata.yaml. See the metactl page for every field.

# metadata.yaml (excerpt)
appName: Sonir
bundleId: app.sonir.app
packageName: app.sonir.app
review:
contactEmail: review@example.com
demoAccountRequired: false
defaultLanguage: ja-JP
locales:
- locale: ja # ASC "ja"; Play normalized to ja-JP
name: "Sonir"
subtitle: "空気録音 × 音響測定"
shortDescription: "録って、測って、比べる。"
description: ""
keywords: "録音,音響,RTA"
supportUrl: https://sonir.app/support
Terminal window
metactl plan -config metadata.yaml # preview — no published writes
metactl apply -config metadata.yaml # write metadata (idempotent)

Once the listing exists, provision billing from iap.yaml. One file drives all three stores; see the iap.yaml reference for every field.

# iap.yaml (excerpt)
appName: Sonir
bundleId: app.sonir.app
packageName: app.sonir.app
entitlement:
key: pro
displayName: Pro access
pricing:
territory: JPN # App Store territory (ISO-3166 alpha-3)
region: JP # Google Play region (ISO-3166 alpha-2)
currency: JPY
products:
- key: yearly
appStoreId: sonir.pro.yearly
playProductId: sonir_pro_yearly
playBasePlanId: yearly
period: P1Y
price: 5400
rcPackage: $rc_annual
localizations:
- { locale: ja, name: "Sonir Pro(年額)", description: "全機能を年額で。" }
- { locale: en-US, name: "Sonir Pro (Yearly)", description: "All features, billed yearly." }
Terminal window
iapctl plan -config iap.yaml # preview — no writes
iapctl apply -config iap.yaml # create missing + correct drift (idempotent)
iapctl plan -config iap.yaml -o json # machine-readable report

Symbols (both CLIs): + create · ~ drift · = exists · - skip · created · error.