- apple testflight wait --build N (progress, timeout, exit 0/1/7) and expire --keep N | --older-than D (dry run, asks) - apple iap-submit: IAP / subscription / group versions into a review submission; first of a type only with the app version (--with-version), --no-submit keeps the draft; read-only MCP plan - apple listing pull, play listing pull (diff, --write), catalog pull (config proposal, never rewritten) - whats-new --languages / check / apply: one file per language, agent or hook translation, per-store limits - apple offer-codes, win-back, promoted, preorder; Play pre-registration as a guide (no API) - apple cpp and apple events: list, create, upload, submit (asks) - reports: Apple sales / finance, Play earnings / sales into .storebound/reports with summary and CSV - triage: TestFlight crash / feedback and Play error reports grouped by signature, device, OS - aso rank: iTunes Search API ranks and competitors, rate limited - ads scan: AdAttributionKit / SKAdNetwork Info.plist and source checks - tests against recorded, sanitized answers; skills, README, CHANGELOG
100 lines
7.9 KiB
Markdown
100 lines
7.9 KiB
Markdown
---
|
|
name: app-store-promotion
|
|
description: App Store promotion tools for a mobile app or game through the App Store Connect API - offer codes for one-time purchases, win-back offers for lapsed subscribers, promoted in-app purchases on the product page, pre-orders per territory, custom product pages (screenshots per audience, deep links) and in-app events, with Apple's limits; Google Play pre-registration as a console guide (no API). Use when the user wants promo codes or discounts for IAPs, to win back subscribers, to feature purchases on the product page, a pre-order or pre-registration launch, ad-specific product pages, or an in-app event.
|
|
---
|
|
|
|
# App Store promotion (offers, promoted IAPs, pre-orders, custom product pages, in-app events)
|
|
|
|
Everything here is App Store Connect API (`cli/lib/offers.ts`, `cli/lib/cpp-events.ts` cite the endpoints and Apple's
|
|
Help pages). Each command has `--dry` and `--json`. What customers can see or redeem is the **owner's** step: those
|
|
commands show the dry run, then ask in the terminal, and none of them is an MCP tool. MCP has the read-only and
|
|
nothing-public parts (`apple_cpp_list | create | upload`, `apple_events_list | create | upload`,
|
|
`apple_preorder_status`).
|
|
|
|
## Offer codes for one-time purchases
|
|
Config, on the product (consumable, non-consumable, non-renewing):
|
|
```ts
|
|
products: [{ id: 'com.example.app.pro', type: 'non_consumable', usd: 9.99, listings: { ... },
|
|
offerCodes: [{ name: 'Launch week', eligibility: ['NON_SPENDER'], usd: 4.99, // or free: true
|
|
customCodes: [{ code: 'LAUNCH26', count: 1000, expires: '2026-12-31' }],
|
|
oneTimeCodes: [{ count: 500, expires: '2027-01-31' }] }] }],
|
|
```
|
|
- `storebound apple offer-codes --dry`, then without `--dry`: creates missing campaigns (by name) with prices for every
|
|
territory (the USA price point of `usd` and its equalizations, or `territories: ['USA', 'DEU']`). A campaign alone
|
|
lets nobody redeem anything.
|
|
- Codes only with `--codes` (asks): custom codes (anyone who has the code, up to 25,000 redemptions, letters and
|
|
digits up to 64) and one-time-use batches (500-25,000, expiry at most 6 months). One-time codes are saved to
|
|
`.storebound/offer-codes/` (gitignored): treat them like vouchers, never paste them into chat, issues or commits.
|
|
- Apple's limits (Help, "Create offer codes for In-App Purchases"): 10 active offers per app, 1 million codes per app
|
|
per quarter; eligibility NON_SPENDER (never bought in the app), ACTIVE_SPENDER (bought in the last 30 days),
|
|
CHURNED_SPENDER (bought more than 30 days ago). Needs iOS 16.3+. Subscription offer codes are `offerCodes` on a
|
|
subscription (`storebound apple subscriptions --codes`, store-subscriptions skill).
|
|
- Play promo codes have no API: Play Console > Monetize > Promo codes.
|
|
|
|
## Win-back offers (subscriptions)
|
|
```ts
|
|
winBack: [{ id: 'come-back-3m', type: 'pay-up-front', duration: 'P3M', usd: 9.99,
|
|
paidMonths: 6, lapsedMonths: { min: 2, max: 12 }, waitMonths: 6, start: '2026-11-01', end: '2026-11-30',
|
|
priority: 'high', promote: false }],
|
|
```
|
|
`storebound apple win-back --dry`, then the owner runs it (asks). Rules (Help, "Set up win-back offers"): the
|
|
subscription must be approved; paid at least 1-24 months or 3, 4, 5 years; lapsed between min and max months (1-24,
|
|
both required); wait between offers 2-24 months; start at the earliest tomorrow and run at least 3 days; at most 5
|
|
running per storefront and 350 per subscription. Reference name, offer id and price cannot change later; the offer id
|
|
must not be reused in the group. An offer goes live by itself on its start date (Manage Subscriptions, the in-app
|
|
offer sheet on iOS 18+, the App Store with `promote: true` and an approved subscription image).
|
|
|
|
## Promoted in-app purchases
|
|
`apple: { promoted: { products: ['com.example.app.pro', 'com.example.app.coins_100'] } }` (order = display order, at
|
|
most 20). `storebound apple promoted --dry` shows the status and the plan: missing ones are created **disabled**, the
|
|
order is set. `--enable` switches them on (asks; they show on the product page once approved). Each needs its
|
|
promotional image in App Store Connect, and the app must handle purchases that start on the App Store (StoreKit
|
|
`PurchaseIntent`; StoreKit 1 `paymentQueue(_:shouldAddStorePayment:for:)`). Without `apple.promoted` the command only
|
|
prints the status.
|
|
|
|
## Pre-orders
|
|
- `storebound apple preorder status` (MCP: `apple_preorder_status`): per territory available, release date, pre-order
|
|
on, publish date, content status.
|
|
- `storebound apple preorder enable --release-date 2026-12-01 [--territories USA,DEU] --dry`, then for real (asks).
|
|
Apple: a version must be approved first; the release date is 2-180 days ahead for a new app (up to 365 days for an
|
|
existing app in a new region); a territory where the app is already released cannot go on pre-order.
|
|
- `storebound apple preorder end [--territories USA]` ends the pre-order and **releases the app there now** (asks).
|
|
- **Google Play pre-registration has no API** (the androidpublisher v3 reference lists no such resource): Play Console
|
|
> Grow users > Pre-registration (set it up on a testing / production track that is not yet available, optional
|
|
pre-registration reward as a one-time product). Pre-registration ads: Google Ads.
|
|
|
|
## Custom product pages
|
|
```ts
|
|
apple: { customProductPages: [{ name: 'Students', variant: 'students', deepLink: 'https://example.com/open/students',
|
|
promotionalText: { en: 'Class notes that sync.', de: '...' } }] },
|
|
```
|
|
1. Screenshots: `storebound shots render --variant students` (a `shots.variants` entry), store-screenshots skill.
|
|
2. `storebound apple cpp create --dry`, then for real (MCP `apple_cpp_create`): the page, its first version and a
|
|
localization per App Store locale.
|
|
3. `storebound apple cpp upload --name Students --dry` (MCP `apple_cpp_upload`): the variant's renders per locale,
|
|
MD5-idempotent.
|
|
4. Owner: `storebound apple cpp submit --name Students` (asks; App Review, independent of an app update), after
|
|
approval `storebound apple cpp visible --name Students` (asks). `apple cpp list` shows each page's URL for ads.
|
|
Limits: 70 pages per app; promotional text 170; the deep link works from iOS / iPadOS 18 once approved. Screenshot
|
|
sets are deprecated in API 4.5.1 in favour of the App Asset Library; they still work and storebound uses them.
|
|
|
|
## In-app events
|
|
```ts
|
|
apple: { events: [{ referenceName: 'Winter Challenge', badge: 'CHALLENGE', purpose: 'KEEP_ACTIVE_USERS_INFORMED',
|
|
schedule: { publishStart: '2026-12-01T00:00:00Z', eventStart: '2026-12-10T00:00:00Z', eventEnd: '2026-12-20T00:00:00Z' },
|
|
listings: { en: { name: 'Winter Challenge', shortDescription: 'Finish ten lists before New Year', longDescription: '...' } },
|
|
media: { card: 'store/events/winter/{lang}-card.png', details: 'store/events/winter/{lang}-details.png' } }] },
|
|
```
|
|
`storebound apple events create --dry` / `upload --name ...` (MCP `apple_events_create` / `apple_events_upload`), then
|
|
the owner runs `storebound apple events submit --name ...` (asks; once approved the event publishes by its schedule).
|
|
Limits (Help, "Offer In-App Events"): reference name 64, event name 30, short description 50, long description 120;
|
|
15 minutes to 31 days; published at most 14 days before it starts; 10 published and 15 approved events per app. Media:
|
|
event card 16:9 (1920x1080 to 3840x2160), details page 9:16 (1080x1920 to 2160x3840), PNG or JPEG; storebound checks
|
|
the size before uploading. Event text is held to the same honesty rules as the listing: a real event in the app.
|
|
|
|
## Gotchas
|
|
- Free offer prices (offer codes, win-back) are sent as territories without a price point, as the API schema allows;
|
|
not yet verified against a live account. A refusal names the field: create that one offer in App Store Connect.
|
|
- Everything with a start date goes live without another click: read the dates with the owner.
|
|
- No manipulative offers: no fake scarcity in event text, no misleading "free" when a purchase is needed
|
|
(`purchaseRequirement` says it).
|