App Store Connect API for subscription offer codes: generating and tracking redemptions programmatically
A step-by-step guide to creating, distributing, and tracking iOS subscription offer code redemptions via the App Store Connect API — covering one-time-use vs. multi-use codes, JWT auth, and the attribution loop that closes your win-back campaigns.
Offer codes are one of iOS's most underused pricing levers. You can give a lapsed subscriber a free month, hand a new user a 50% discount, or reward a loyalty segment — all without touching your public price point. The challenge is that most developers generate codes manually in App Store Connect's web UI, one campaign at a time. That approach breaks down fast when you're running segmented win-back flows, instrumenting a referral programme, or integrating offers into an automated lifecycle campaign.
The App Store Connect API exposes offer code management as first-class operations. This post walks through the full lifecycle: creating offer code resources, generating batches of one-time-use codes, distributing multi-use codes, and reading redemption data back for downstream analytics. If you haven't set up API authentication yet, the App Store Connect API JWT walkthrough is the right prerequisite.
Offer code types: one-time-use vs. multi-use
Apple supports two distinct offer code formats, and picking the wrong one for a campaign is a common mistake worth getting right before you touch the API.
| Type | Format | Redemption limit | Best use case |
|---|---|---|---|
| One-time use | Random alphanumeric string (~20 chars) | Single subscriber | Targeted win-back, CRM campaigns, referral rewards |
| Multi-use (custom code) | Developer-defined string (e.g. SPRING50) | Up to 25,000 total redemptions | Broad campaigns, influencer links, QR codes |
One-time-use codes let you send a unique link to each lapsed subscriber through your CRM without worrying about codes leaking to coupon aggregator sites. Multi-use codes are easier to distribute publicly — a promo banner, a podcast mention, a printed QR code — but cap out at 25,000 redemptions each and carry obvious exposure risk. For win-back campaigns targeting a known lapsed cohort, one-time-use codes are almost always the right choice.
Both types share the same two-step API pattern: first create an offer code resource that defines the discount terms, then generate code batches from that resource. The separation keeps your offer definitions reusable across campaigns and lets you deactivate a resource without voiding all previously distributed codes at once.
Eligibility check first: Offer codes only work for subscribers who qualify for the specific offer type. A subscriber who already redeemed an introductory offer cannot redeem an introductory offer code again. Review Apple's offer eligibility rules — covered in depth in the offer eligibility guide — before you segment your audience or you'll generate codes that silently fail to convert.
Creating an offer code resource via the API
The first API call establishes the offer parameters. Send a POST to https://api.appstoreconnect.apple.com/v1/subscriptionOfferCodes with an Authorization: Bearer <JWT> header and a JSON body like this:
{
"data": {
"type": "subscriptionOfferCodes",
"attributes": {
"name": "Win-back Q4 2026 — 1 month free",
"customerEligibilityPaidSubscription": true,
"customerEligibilityFamilyShared": false,
"customerEligibilityNewSubscribers": false,
"offerEligibility": "ONCE_PER_SUBSCRIBER",
"duration": "ONE_MONTH",
"offerMode": "FREE_TRIAL",
"numberOfPeriods": 1
},
"relationships": {
"subscription": {
"data": {
"type": "subscriptions",
"id": "YOUR_SUBSCRIPTION_PRODUCT_ID"
}
}
}
}
}
A few attributes that frequently cause confusion:
customerEligibilityPaidSubscription: truetargets lapsed paid subscribers — the classic win-back audience. FlipcustomerEligibilityNewSubscriberstotruefor acquisition-focused campaigns instead.offerModeacceptsFREE_TRIAL,PAY_AS_YOU_GO, orPAY_UP_FRONT. For a free-month win-back,FREE_TRIALis correct. For a discounted price rather than a free period, usePAY_AS_YOU_GOand supply asubscriptionPricePointrelationship pointing to the reduced price point.offerEligibility: "ONCE_PER_SUBSCRIBER"means each Apple ID can only redeem this offer once, regardless of how many unique codes you distribute. This prevents abuse while still allowing you to issue individual codes per user. The alternative"ONCE_PER_DEVICE"is stricter and rarely appropriate for win-back use cases.
The successful 201 response returns a resource object with an id you will use in every subsequent call for this offer definition. Store this ID in your campaign metadata.
Generating and distributing one-time-use codes
Once the offer resource exists, generate a batch of unique codes by POSTing to /v1/subscriptionOfferCodes/{id}/oneTimeUseCodes:
{
"data": {
"type": "subscriptionOfferCodeOneTimeUseCodes",
"attributes": {
"numberOfCodes": 500
},
"relationships": {
"offerCode": {
"data": {
"type": "subscriptionOfferCodes",
"id": "OFFER_CODE_RESOURCE_ID"
}
}
}
}
}
Apple caps the size of each batch request. For win-back campaigns targeting thousands of users, loop through multiple batch requests and accumulate the code strings. The response includes a values array of the generated code strings — write these to your data store immediately, as the raw strings may not always be retrievable on a later GET call depending on batch size and Apple's response format.
Each code redeems via a URL that iOS handles natively: https://apps.apple.com/redeem?ctx=offercodes&id=BUNDLE_ID&code=CODE. Embed this URL directly in a push notification, transactional email, or in-app message. When a lapsed user taps it, iOS presents the StoreKit subscription sheet — your app does not need any special code to handle the resulting transaction beyond your existing purchase listener.
Multi-use custom codes follow the same two-step pattern but POST to /v1/subscriptionOfferCodes/{id}/customCodes with a customCode attribute (your human-readable string) instead of a numberOfCodes count. Apple validates uniqueness across your entire app — you cannot reuse a string already claimed by any other offer on the same subscription product.
Reading redemption data and closing the attribution loop
Generating and distributing codes is the first half. Measuring what converts is the second half, and Apple provides two complementary channels for it.
App Store Connect Sales and Trends reports include an offer-related column in subscription event rows. Each offer code redemption surfaces as a subscription event with the offer code resource identifier attached. You can pull these reports nightly via the Reports API and roll up redemption counts per campaign. This is the right channel for aggregate campaign reporting and comparing win-back rate across cohorts.
App Store Server Notifications v2 fire in near real-time on SUBSCRIBED and DID_RENEW events. The notification payload includes an offerType field — value 3 indicates an offer code redemption — and an offerIdentifier that matches the name you gave the offer code resource. This lets you update your CRM within seconds of a lapsed subscriber converting, removing them from future win-back sequences before the next campaign run picks them up again.
| Data channel | Latency | Granularity | Best for |
|---|---|---|---|
| Sales and Trends reports | T+1 day | Aggregate per offer resource | Campaign-level attribution, A/B comparison |
| Server Notifications v2 | Near real-time (<60 s) | Per-subscriber event | CRM tagging, suppression list updates |
| App Store Connect API (read) | Near real-time | Code resource metadata | Remaining budget, active/inactive status |
RevenueCat's data has consistently shown that win-back campaigns recover a material share of lapsed subscribers, with users who lapsed within the previous 60–90 days converting at meaningfully higher rates than those dormant for over six months. That recency signal is worth building into your CRM segmentation query before you generate codes — there is little point distributing 2,000 one-time-use codes to a cold audience with near-zero redemption intent.
Deactivating a campaign: PATCH the subscriptionOfferCodes resource with "active": false to stop new redemptions immediately. Previously distributed links will fail gracefully for users who tap them after deactivation. Build this into your campaign close workflow — especially for multi-use codes — to prevent redemptions after the offer window ends.
Putting it all together: a minimal campaign runner
The practical implementation that works well for most subscription apps combines the API calls above into a lightweight campaign script that can run on any server or as a scheduled cloud function:
- Query your data warehouse or CRM for subscribers who lapsed 14–60 days ago and have not redeemed an offer in the past 90 days. Apply any additional filters — geography, plan tier, app version — that your win-back hypothesis requires.
- Create or reuse an offer code resource. If the discount terms are unchanged from a previous campaign, reuse the existing resource ID rather than creating a new one. Each resource can generate multiple independent batches of codes.
- Generate a batch of one-time-use codes sized to the segment. Loop through batch requests if the segment exceeds Apple's per-request cap.
- Distribute via your preferred channel. For transactional email, pair each user record with its unique code and send through your ESP. For push, trigger a campaign via your push provider. For in-app messaging, store codes server-side and surface the redemption URL on next app open.
- Listen for Server Notifications with
offerType = 3and tag each converting subscriber in your CRM, removing them from future win-back sequences. - Pull campaign metrics from Sales and Trends on day 7 and day 30 for attribution reporting. PATCH the offer resource to inactive when the campaign window closes.
For teams already running App Store pricing automations, this runner slots naturally into the same authenticated API client. The JWT token, base URL, and pagination patterns are identical across all App Store Connect API endpoints, so the incremental integration cost is low once the foundation is in place.
One operational note worth flagging: Apple does not expose a per-code "has this been redeemed?" lookup via the public API. Attribution flows back through Server Notifications and daily reports, not through polling individual code strings. Design your data pipeline around those two channels from the start rather than trying to retrofit them after launch.
Sources and further reading
- Apple Developer Documentation: Subscription Offer Codes — App Store Connect API reference
- Apple Developer Documentation: Implementing Offer Codes in Your App (StoreKit)
- Apple Developer Documentation: App Store Server Notifications v2
- RevenueCat: iOS Subscription Offer Codes — strategy and implementation guide
- WWDC 2021 Session 10175: Subscription Offer Codes (Apple Developer)
Share this post
Ready to put this into practice?
AppsOps is the first App Store ops dashboard — PPP-fair pricing for 175 App Store territories, AI metadata localization in 39 languages, AI screenshot localization for 14 Apple device classes, and one-click App Store Connect API push — all from one dashboard, all for $19/month.
Try AppsOps free — no card →