Monetization Control Plane
Author, plan, apply, and drift-detect your RevenueCat subscription config as Infrastructure-as-Code.
The monetization control plane lets you manage your app's subscription configuration — entitlements, products, offerings, packages, and paywalls — as Infrastructure-as-Code, on top of your existing RevenueCat project. New to this? Walk the whole flow first in the Monetization Quickstart — this page is the full reference.
Additive changes apply automatically; destructive ones require explicit confirmation. Apply
creates entitlements, offerings, packages, and paywall drafts and attaches products with no
confirmation. Destructive changes — detaching a product, archiving an entitlement or offering, or
removing a package — wait until you confirm with --confirm (or confirm set to the
plan_hash); without it they are reported but not run. Applies are durable and pollable: you get
an operation handle, writes are paced against the provider's rate limits and idempotency-keyed, an
interrupted apply resumes safely, and apply reads back every write, stops on the first failure
without rolling back, and never archives the offering customers currently see or detaches a
product a live customer resolves (unless you explicitly allow it).
Two changes stay in the RevenueCat dashboard because RevenueCat has no API for them: choosing the default (current) offering ("Make Default") and publishing a paywall live to an offering. Amba authors the paywall draft and tells you the exact dashboard step for the rest — it never silently skips them.
Store products
Apply can now provision your store products through RevenueCat. Declare a product (its store, store identifier, type, and optional provisioning metadata) and a single apply:
- Registers it with RevenueCat (the product record, attached to the right app) for any store.
- Creates the real product in App Store Connect through RevenueCat's store
connection — declare
store_metadata.push_to_store: trueon anapp_storeproduct, plusduration(ONE_WEEK|ONE_MONTH|TWO_MONTHS|THREE_MONTHS|SIX_MONTHS|ONE_YEAR) andsubscription_group_namefor subscriptions. One-time purchase types need no extra metadata.
Store catalog writes never run off an up-front confirm. A plan with store-product steps always
starts in a waiting state — even if you passed --confirm — and only runs after you explicitly
approve the started apply (re-run amba monetization apply --confirm). Creating real store
products is the one place a second deliberate look is structurally required.
The human-floor checklist
Some store-side steps have no API — anywhere, for anyone. The plan surfaces them
as a human_floor checklist with the exact console location for each, so
you (or your agent) know precisely what remains:
- Store SKUs outside the App Store — RevenueCat's store push covers App Store Connect only. Google Play / Stripe / Amazon products are registered with RevenueCat by apply, but the SKU itself is created in the store console (the checklist carries your declared price/duration so it's a copy-paste job).
- Pricing — no API sets store prices. After a product exists, set its price
(and intro/trial offers) in the store console. Your declared
store_metadata.priceis echoed in the checklist. - Agreements, banking, and tax — the Paid Applications Agreement (App Store Connect → Business) and the payments profile (Play Console → Setup) are one-time account-level steps.
- App review — newly created App Store in-app purchases ship with your next app review submission.
Blocking gaps — a product referenced but never declared, or a store with no
connected RevenueCat app — fail the apply up front (422) with the exact fix.
What you get
- Plan — a three-way diff between the config you've declared in Amba, the baseline you last adopted, and the live config in RevenueCat. It shows the operations an apply would perform (create / update / attach / archive / set-current — including store-product creation steps), a store-floor preflight (blocking gaps the apply can't create), a human-floor checklist (store-side steps no API performs, with exact console locations), and any drift. Store products are managed objects like everything else — out-of-band edits to them surface as drift.
- Apply — push your declared config to RevenueCat as a durable,
pollable operation: starting an apply returns an
operation_idyou poll to completion, writes are paced against RevenueCat's API rate limits, every write carries an idempotency key (an interrupted apply resumes safely, never double-creating), and the run survives restarts. Additive ops (create entitlements / offerings / packages / paywall drafts; attach products) apply automatically. Destructive ops (detach, archive, remove a package) are gated: they apply only when you confirm the exactplan_hash— pending applies wait for your confirmation. Pass theplan_hashfrom a fresh plan; apply refuses if RevenueCat drifted since then. Setting the default offering and publishing a paywall live are surfaced as dashboard steps (no RevenueCat API). - Drift detection — surfaces RevenueCat objects under Amba's management that changed out-of-band (someone edited them directly in the RevenueCat dashboard). A daily background sweep records drift automatically.
- Export — snapshot your live RevenueCat config into a declarative bundle you can review and version-control.
- Adopt — record the live config as Amba's managed baseline so the next plan is a clean no-op and future out-of-band edits surface as drift. Adopt writes only Amba state; it never changes RevenueCat. It's idempotent.
Prerequisites
Connect RevenueCat as an integration with a secret API key. Plan, drift, and
export work with a read-scoped key (project_configuration:*:read). To
apply, supply a read-write key (project_configuration:*:read_write) as
secret_api_key_write on the integration — or use one full-access key for both.
Amba resolves keys server-side; they are never returned to a client.
To push products into App Store Connect, connect your App Store Connect API key to RevenueCat (RevenueCat dashboard → your app's configuration) — apply creates the products through that connection. What no API can do — prices, agreements/banking/tax, app review, and store SKUs outside the App Store — stays on the plan's human-floor checklist with the exact console location for each.
Plan
Returns the op-list, the store-floor preflight, and drift. Nothing is applied — this is a preview.
Apply
Pushes your declared config to RevenueCat. It computes a fresh plan first, then
starts a durable apply and returns an operation_id — the CLI polls it to
completion for you; over the API, poll
GET /v1/admin/projects/:projectId/operations/:operationId until succeeded |
failed. The apply paces its writes against RevenueCat's API rate limits (a
large plan takes a few minutes), and every write carries an idempotency key, so
an interrupted apply resumes exactly where it left off without double-creating
anything (a re-attempted App Store push that already landed resolves cleanly).
If a blocking store-floor gap exists it stops up front and tells you exactly
what to fix.
If the plan includes destructive ops (detach a product, archive an entitlement/offering/product, remove a package) or store-product creation steps, the apply reports them and waits for your confirmation without running them — review (including the human-floor checklist), then confirm:
Confirmation approves the exact plan_hash you reviewed. If the plan changed
between plan and confirm, the confirmation is rejected and nothing destructive
runs — re-plan and apply again. An unconfirmed apply expires on its own after
24 hours.
--confirm executes the gated ops in a safe order (store-product creates first
— before anything that references them — then detach → remove package → archive
entitlement → archive product → archive offering, dead-last), and:
- it refuses to archive the offering customers currently see (RevenueCat has no API to move the default pointer — make a different offering the default in the dashboard first);
- it refuses to detach the last product a live customer resolves unless you
add
--allow-detach-liveto accept the impact.
For configuration that drifted out-of-band, choose how to reconcile:
Sandbox-first (optional). If you configure a sandbox RevenueCat project on the integration, the gated apply runs against the sandbox first plus a synthetic purchase→entitlement-resolve check, and only proceeds to production if it passes.
Drift
Shows only the Amba-managed objects whose live RevenueCat state has changed since you last adopted them.
The control plane is deliberately leaky: the RevenueCat dashboard stays
fully usable, and out-of-band edits are never blocked or silently reverted —
they're observed (every plan plus a daily background sweep records them as
drift) and you decide per change whether to keep it (--reconcile adopt) or
converge back to your declared config (--reconcile amba, the default).
Export
Writes the live config to a declarative bundle. Omit --file to print to
stdout.
Adopt
Records the live config as your managed baseline. After adopting, the next
amba monetization plan is a clean no-op, and any later dashboard edit shows up
as drift.
Declarative authoring with promotion bundles
The monetization section is part of the promotion bundle,
so you can author your subscription config once and promote it between projects
with amba export / amba apply. Importing writes Amba's declared definitions
only — it doesn't touch RevenueCat, and it leaves the adopted baseline empty so
the next plan shows your imported config as pending create ops until you
adopt.
For agents (MCP)
| Tool | What it does |
|---|---|
amba_monetization_plan | Preview the three-way diff + store-floor + human-floor checklist + drift; returns plan_hash |
amba_monetization_apply | Apply the declared config (additive auto; destructive gated behind confirm; store steps need an explicit confirm) |
amba_monetization_drift | Out-of-band changes to managed objects (read-only) |
amba_monetization_export | Snapshot the live config to a declarative bundle (read-only) |
amba_monetization_definitions_list | List the declared definitions (read-only) |
amba_monetization_adopt | Record the live config as the managed baseline (writes Amba state only) |