Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Manage the monetization catalog — subscriptions and one-time products — as files with gplay `subscriptions` and `iap`; the directory is a mirror, so a live product with no file is a delete. Use when putting subscriptions or IAP under version control, editing a product's prices/offers/listings, reviewing catalog drift in CI, promoting a legacy in-app product to the v2 model, or migrating live subscribers to a new price.
.claude/skills/pollyglot-gplay-monetization/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | -14% | 0% |
| case-02 | ✗→✓ | ▲ Improved | -32% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 40% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 51% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 3% | 0% |
gplay subscriptions and gplay iap own the monetization catalog as declarative, version-controlled files (ADR-0041). Shared conventions are in gplay-cli-usage. Both namespaces are [experimental].
| Namespace | What it holds | |---|---| | subscriptions | subscriptions, their base plans (config + per-territory prices), their offers, lifecycle state | | iap | one-time products, v2 monetization.onetimeproducts ∪ legacy inappproducts |
Both sit outside the Edit lifecycle (no editId): gplay edits begin does not batch them.
pull → edit the .json files → apply --dry-run → apply [--confirm]One <productId>.json per product, holding the API resource verbatim minus server-derived noise. pull then apply with no edits is a guaranteed no-op. Commit the directory; the diff in review is the catalog change.
metadatametadata apply is additive: a locale live online but absent on disk is left alone. Monetization is the opposite. The directory is the complete declared catalog: a live subscription, product or offer with no file is a delete in the plan.
pull before every apply. apply refuses an empty directory whenthe app has live products, but a partially populated one is a legitimate plan full of deletes.
pull is destructive locally too, and refuses to erase a populateddirectory when the live catalog reads back empty (a mis-set --package or a scope loss): that refusal is a signal, not an obstacle to work around.
to delete one with a published base plan.
| Situation | Gate | Exit without it | |---|---|---| | Plan contains any delete (product, base plan or offer) | --confirm | 3 | | iap apply cancels a pre-order offer (irreversible) | --confirm | 3 | | iap apply promotes a live legacy product to v2 | --migrate | 3 | | subscriptions prices migrate (reprices live subscribers) | --confirm | 3 | | Creates, patches, state changes (reversible) | none, they run directly | n/a |
bashgplay subscriptions pull # → ./monetization/subscriptions/*.json # …edit the files… gplay subscriptions apply --dry-run # ONLINE read, prints the plan, changes nothing gplay subscriptions apply # creates/patches/state changes gplay subscriptions apply --confirm # …when the plan also deletes
per-territory regionalConfigs prices) is declared inline under basePlans and patched with the subscription; the API has no create/patch on the sub-resource. Its endpoints only manage state, subscriber price migration, and deletion.
BASE_PLAN_NOT_DRAFT): retire it in two applies, state: INACTIVE first, then drop it from the file.
pull nests each offer underbasePlans[].offers, a file construct the API resource does not carry. apply splits them back out and reconciles them through the offers endpoints under the key productId/basePlanId/offerId.
state: (ACTIVE/INACTIVE, omit to leave it unmanaged) reconcilesvia :activate/:deactivate. DRAFT from anything, or INACTIVE from DRAFT, is a usage error naming the transition.
listings, taxAndComplianceSettings,restrictedPaymentCountries, basePlans. The updateMask is exactly the changed managed fields; nothing outside that projection drifts or diffs. archived is not reconciled (deprecated/output-only upstream).
bash# Derive per-region prices from one base price: gplay subscriptions prices convert --price 4.99 --currency USD --output json
Paste the returned Money objects into a base plan's regionalConfigs, then apply --dry-run. convert is online (today's rates via convertRegionPrices): it needs a credential and the package axis, and mutates nothing.
bash# Reprice EXISTING subscribers, money-moving, one base plan per call: gplay subscriptions prices migrate \ --product premium --base-plan monthly \ --region FR --region DE \ --oldest 2026-01-01T00:00:00Z \ --price-increase-type opt-in \ --dry-run # offline preview, unlike apply --dry-run gplay subscriptions prices migrate … --confirm
This is the one deliberate exception to "editing files never touches a live purchaser." apply changes what new buyers pay; migrate changes what existing subscribers pay. An apply diff never triggers a migration.
iap)bashgplay iap pull # v2 ∪ legacy → ./monetization/iap/*.json gplay iap apply --dry-run gplay iap apply [--confirm] [--migrate]
pull unions the v2 and legacy surfaces by product ID, and a file's origin is its shape; no gplay-invented marker:
| Field present | Model | |---|---| | sku | legacy inappproducts | | productId | v2 onetimeproducts |
Purchase options and offers carry state too, same stance as subscriptions; CANCELLED (pre-order offer) is the one-way case gated in the table above.
Legacy is inert: gplay never creates, edits or deletes a legacy product, the only gesture is the one-way promotion to v2 (rewrite the file with productId and apply with --migrate; rehearse with --dry-run first). When a legacy file is involved (an unexpected refusal, or the question of why pull reads two surfaces), read iap-legacy.md.
bashgplay subscriptions apply --dry-run --output json # the plan, a gplay-owned shape gplay iap apply --dry-run --output json
apply --output json emits the plan, not an API echo: {package, dryRun, changes[], summary{…}, requires[]}, where each change carries op (create/patch/delete/activate/deactivate, plus migrate and cancel on iap) and its identity.
A drift check is one line, fail the job when the plan is non-empty:
bashgplay subscriptions apply --dry-run --output json | jq -e '.changes | length == 0'
No permission alias maps to monetization, so the 403 hint (exit 11) names no capability: grant the service account Monetization setup on the app in Play Console (Users & permissions), then retry.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | fail→pass | 35,950 | 15,088 | -58% | 1 | 1 | 0% | 2,768 | 2,388 | -14% | 0 | 0 | — |
case-02 | fail→pass | 23,759 | 10,295 | -57% | 1 | 1 | 0% | 3,703 | 2,519 | -32% | 0 | 0 | — |
case-03 | fail→pass | 41,254 | 5,949 | -86% | 1 | 1 | 0% | 1,835 | 2,566 | +40% | 0 | 0 | — |
case-04 | fail→pass | 45,056 | 13,700 | -70% | 1 | 1 | 0% | 1,934 | 2,924 | +51% | 0 | 0 | — |
case-05 | pass→pass | 19,500 | 11,550 | -41% | 1 | 1 | 0% | 2,258 | 2,727 | +21% | 0 | 0 | — |
case-06 | fail→pass | 19,624 | 7,544 | -62% | 1 | 1 | 0% | 2,433 | 2,503 | +3% | 0 | 0 | — |
case-07 | fail→pass | 24,243 | 14,738 | -39% | 1 | 1 | 0% | 1,665 | 2,827 | +70% | 0 | 0 | — |
case-08 | fail→pass | 11,973 | 11,349 | -5% | 1 | 1 | 0% | 1,734 | 2,534 | +46% | 0 | 0 | — |
case-09 | pass→pass | 11,618 | 6,562 | -44% | 1 | 1 | 0% | 1,728 | 2,299 | +33% | 0 | 0 | — |
case-10 | fail→pass | 35,850 | 3,210 | -91% | 1 | 1 | 0% | 2,629 | 2,172 | -17% | 0 | 0 | — |
case-11 | fail→pass | 18,961 | 8,223 | -57% | 1 | 1 | 0% | 2,995 | 2,186 | -27% | 0 | 0 | — |
case-12 | pass→pass | 10,414 | 6,078 | -42% | 1 | 1 | 0% | 1,575 | 2,555 | +62% | 0 | 0 | — |
case-13 | pass→pass | 21,203 | 10,519 | -50% | 1 | 1 | 0% | 2,425 | 2,303 | -5% | 0 | 0 | — |
case-14 | fail→pass | 13,989 | 10,818 | -23% | 1 | 1 | 0% | 2,031 | 2,412 | +19% | 0 | 0 | — |
case-20 | fail→pass | 19,164 | 10,823 | -44% | 1 | 1 | 0% | 2,118 | 2,615 | +23% | 0 | 0 | — |
case-15 | fail→pass | 38,593 | 5,708 | -85% | 1 | 1 | 0% | 2,283 | 2,345 | +3% | 0 | 0 | — |
case-16 | fail→pass | 17,625 | 11,308 | -36% | 1 | 1 | 0% | 3,013 | 2,714 | -10% | 0 | 0 | — |
case-17 | fail→pass | 15,115 | 16,873 | +12% | 1 | 1 | 0% | 2,195 | 2,236 | +2% | 0 | 0 | — |
case-18 | pass→pass | 26,069 | 12,057 | -54% | 1 | 1 | 0% | 1,697 | 2,619 | +54% | 0 | 0 | — |
case-19 | fail→pass | 36,699 | 7,570 | -79% | 1 | 1 | 0% | 1,363 | 2,545 | +87% | 0 | 0 | — |
case-21 | fail→pass | 17,888 | 7,662 | -57% | 1 | 1 | 0% | 2,593 | 3,017 | +16% | 0 | 0 | — |
case-22 | fail→pass | 13,859 | 4,862 | -65% | 1 | 1 | 0% | 2,219 | 2,246 | +1% | 0 | 0 | — |
case-23 | fail→pass | 15,544 | 4,002 | -74% | 1 | 1 | 0% | 2,171 | 2,326 | +7% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 22 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +78 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
| Model | Method | Date | Lift |
|---|---|---|---|
| gemini-3.6-flash | verified | 8/23/2026 | +82% |
| gemini-3.6-flash | verified | 8/4/2026 | +77% |
Other measured skills in the registry, with their headline benchmark lift.