One desired state, three stores
Describe your subscriptions and one-time products once in iap.yaml.
iapctl reconciles App Store Connect, Google Play, and
RevenueCat — idempotent, matched by identifier before creating.
iap.yaml → App Store Connect, Google Play, and RevenueCat. plan/apply, GitOps, and continuous drift detection.One desired state, three stores
Describe your subscriptions and one-time products once in iap.yaml.
iapctl reconciles App Store Connect, Google Play, and
RevenueCat — idempotent, matched by identifier before creating.
plan / apply
plan previews changes (no writes); apply creates what’s missing and
corrects prices in place. Re-run any time — it’s idempotent.
Drift detection
Someone changed a price in a store console? iapctl plan flags it as
drift (current → desired), distinct from a first-time create. Run it on
a schedule to turn one-time setup into a continuous guardrail.
GitOps, keys stay yours
PR → sticky plan comment. Merge → apply. Your store keys never leave
your CI’s secret store — there’s no service that holds them.
App Review metadata, too
metactl provisions App Review text metadata — name, descriptions,
keywords, review contact, age rating — from metadata.yaml, with the same
plan/apply, GitOps, and drift model. In practice you set up metadata
first, then billing.
Keeping three store catalogs consistent by hand is painful, and nothing tells you when a price was changed in a console. storectl makes the catalog a versioned desired state you review in PRs and monitor continuously — the same model Terraform brought to infrastructure, focused on the app-store billing catalog.
Two CLIs share the model. The usual flow is metadata first, then billing:
metactl provisions App Review text metadata from metadata.yaml, then
iapctl provisions the billing catalog from iap.yaml.
-o json, versioned) for CI comments, dashboards, and notifications.curl … | sh, verified with checksums (and cosign).Ready? Head to Getting started.