WAITING_FOR_REVIEW; the review outcome is not in my record.On October 29 I went to submit the 1.0 of one of my apps, and that version carried the app's first in-app purchase, a non-consumable product. My normal release path submits the version, so on this release it would have left the product behind. I did the setup through the API, got both items into a single submission through a short web flow with two traps in it, and then checked the result through the API.
My record ends at WAITING_FOR_REVIEW for both items, so I can't tell you how the review went.
Apple's help page Overview for configuring In-App Purchases states the rule directly:
If you're submitting your first In-App Purchase, you must submit it with a new version of your app.
The product screen in App Store Connect shows its own wording of the same rule: "Your first In-App Purchase must be submitted with a new app version".
The rule is about the first one, so everything below applies only to the first.
The API page Managing In-App Purchases settles the automation question in an Important note:
Submit your first In-App Purchase together with an app binary submission through appstoreconnect.apple.com. The review-submission API calls below don’t apply to that first submission. For subsequent In-App Purchases, submit using the API endpoints below without an associated app binary submission.
So the documented route for a first purchase is the website. The API flow the note points to for later purchases is POST /v1/reviewSubmissions, a reviewSubmissionItems item with an inAppPurchaseVersion relationship, then submitted: true. The same page marks POST /v1/inAppPurchaseSubmissions deprecated as of 4.4.1 and sends new integrations to that review submissions workflow.
I followed the note and never sent those calls for a first purchase, so I have no API error to show you. One warning about how I checked: when I read the page through a scraper on November 4, the markdown had dropped the Important note, and I nearly wrote that Apple's API docs didn't mention the first purchase. The page's raw doc data has it. If a claim hangs on an Apple doc page, read the note boxes on the page itself.
My normal release path is fastlane submit_review and a 3-step reviewSubmissions script. Both submit the version. If I had used either one here, the purchase would not have gone in. A version approved without its product leaves one of two endings: a buy button that gets rejected, or a product waiting for the next version. I didn't run it that way, so those are the outcomes I was steering around and not something I observed.
One read also failed in a way that is easy to over-interpret. I asked the items endpoint to include the product:
GET /v1/reviewSubmissions/{id}/items?include=inAppPurchaseV2
-> 400 'inAppPurchaseV2' is not a valid relationship name
That 400 says the name is not accepted on this read. It says nothing about submitting a first purchase, and I don't use it as evidence for that. Apple's note is the evidence. It does mean this include can't show you the product inside a submission, which comes back when you verify the result.
Both traps involve the Add for Review button, which appears on the version screen and on the product screen.
Add for Review on the product screen doesn't add the product directly. It opens a menu with two entries:
Nothing happens unless you choose the existing draft. "Create new submission" would split the product from the version, which is the separation Apple's rule says not to make, so I did not take that entry.
Add for Review on the version screen first opens a "submission draft" dialog, and that dialog covers the Add for Review button on the product screen. My first click on the product button returned OK and had no effect, because the dialog was covering it. After I closed the dialog and clicked again, the menu appeared, and a MutationObserver caught its text.
I didn't check why a click on a covered button comes back OK. The practical result is that the OK told me nothing about whether the menu had opened.
After the version's Add for Review, a warning said content rights info must be set in app info. contentRightsDeclaration is an app-level value, so a version metadata push doesn't fill it. Apps that have already shipped have it set, so you only hit this on a first app. It takes one API call, so I list it with the API work below, where it gets done before the web step starts.
The rule I kept: do everything the API can do first. That shrinks the web part to making the draft, putting the product in the draft, confirming two items, and submitting.
The API part uses key auth with a JWT of at most 1200 seconds. I wrote about that JWT expiry hard cap in Three App Store Connect API Hard Limits That Only Bite at a Fast Release Cadence. The calls:
PATCH /v1/appStoreVersions/{ver}/relationships/build with {data:{type:"builds",id}}, which returns 204.PATCH /v1/appStoreVersions/{ver} with releaseType as MANUAL or AFTER_APPROVAL.PATCH /v1/apps/{app} with contentRightsDeclaration as DOES_NOT_USE_THIRD_PARTY_CONTENT or USES_THIRD_PARTY_CONTENT.GET /v2/inAppPurchases/{id} should return READY_TO_SUBMIT. By this point the review screenshot, notes, price, and localizations were done.Then the web part, in this order:
The version goes first so the product's menu has an existing draft to choose. The close step is there because leaving the dialog open is what made my first click do nothing.
A successful click is not evidence. I had just seen one return OK and change nothing, so I check the item count on screen and through the API, and I don't submit a draft without the product. After submitting, I ran these reads:
GET /v1/reviewSubmissions?filter[app]=<appId> -> state WAITING_FOR_REVIEW
GET /v1/reviewSubmissions/{id}/items -> 2 items
GET /v2/inAppPurchases/{id} -> WAITING_FOR_REVIEW
In the items response, the version item has an appStoreVersion relationship and the product item shows no relationship. So the product's item doesn't identify itself as a purchase there, and the include from earlier returns a 400. The count comes from that response and the product's state comes from its own endpoint, so I run all of these reads.
If you set releaseType to MANUAL, a person presses release after approval.
WAITING_FOR_REVIEW for both items, so nothing here shows that this submission passes review.This procedure is the one I know works up to the review queue. The next thing to check is how the review ends, and whether a first subscription needs the same path, which I haven't run.
Apple's help page says no: "If you're submitting your first In-App Purchase, you must submit it with a new version of your app." The App Store Connect product screen says the same in its own wording. Apple's API page says later purchases can go through the API endpoints without an app binary submission, so this applies only to the first.
Apple's API page "Managing In-App Purchases" says no: "Submit your first In-App Purchase together with an app binary submission through appstoreconnect.apple.com. The review-submission API calls below don’t apply to that first submission." I followed that and never sent those calls for a first purchase, so I have no observed API error to report.
I hit two causes. The button opens a menu with "iOS submission draft (1)" and "Create new submission", and nothing happens unless you choose the existing draft. Before that, the "submission draft" dialog from the version screen covered the product button, so my first click returned OK with no effect. After I closed the dialog and clicked again, the menu appeared.
The draft dialog must show 2 items ready before you press Submit for Review. Afterward, GET /v1/reviewSubmissions?filter[app]=<appId> should show state WAITING_FOR_REVIEW, GET /v1/reviewSubmissions/{id}/items should return 2 items, and GET /v2/inAppPurchases/{id} should show WAITING_FOR_REVIEW. In the items response the version item has an appStoreVersion relationship and the product item shows no relationship.
contentRightsDeclaration is an app-level value, so a version metadata push doesn't fill it. Apps that have already shipped have it set, so you only hit the warning on a first app. In the order that worked, PATCH /v1/apps/{app} sets it before the web step.