On Speech Route I could see subscription products in App Store Connect. They had names I recognised. They were not ready.
StoreKit still returned an empty list. The paywall opened with nothing to buy. Guideline 2.1 treated the app as incomplete, because to a reviewer it was.
The products were in a half-finished state. That is enough. A row in the In-App Purchases table is not a product StoreKit can sell.
Submittable is a specific state
Every auto-renewable subscription needs to reach a state Apple will actually return: display name, description, price, and a review screenshot. Until those are filled, the product exists in the UI and does not exist to the binary.
The screenshot is the field I underestimated. It is not marketing art. It is the image a reviewer is supposed to see of the purchase. Leave it blank and the product stays incomplete, which looks exactly like a StoreKit bug when you fetch it.
Display name and description are not optional flavour. They are the metadata the store uses to decide the product is real. A product you created in five minutes and meant to finish later is the product that wasted my first hours in Xcode.
The code cannot tell you this
The fetch succeeds. The identifiers can match. The response is still [].
There is no field in the StoreKit error that says "add a review screenshot." I wrote about that empty list as a debugging trap in Why StoreKit Returned No Products. This is the product-level half of it. The account-level half is the Paid Applications Agreement.
On Speech Route, some of the missing metadata was information the client had not provided yet. I could not invent a localised display name and a review capture from an empty App Store Connect record. The paywall code was not going to fix that.
What I refuse to fetch against now
I do not run the product request until each subscription shows as ready to submit.
That means I open the product, not the list. I check the display name, the description, a price for the storefront I am testing, and a screenshot that is actually attached. Then I match the product ID in the binary to that record, character for character.
If any of those are missing, I do not debug StoreKit. I finish the product. Reviewers never see your half-finished IAP form. They see a paywall with nothing on it, and 2.1 is the guideline that names that. The rest of that first submission is in the eight-rejections post.