AI News Hub Logo

AI News Hub

The ASC API's 3-step review submission flow (and why appStoreVersionSubmissions is gone)

DEV Community
孫昊

I spent yesterday automating app submissions for 4 iOS apps. Straightforward task: hit an endpoint, POST the app version to App Review, done. Except the endpoint I was reaching for — /appStoreVersionSubmissions — is deprecated. Apple's replaced it with a three-step choreography using /reviewSubmissions. No official migration guide. Just a quiet documentation update and broken automation scripts all over GitHub. Here's the flow, with working curl examples. curl -X POST https://api.appstoreconnect.apple.com/v1/appStoreVersionSubmissions \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{"data":{"type":"appStoreVersionSubmissions","relationships":{"appStoreVersion":{"data":{"id":"","type":"appStoreVersions"}}}}}' This worked. One endpoint, one request, done. But Apple deprecated it (no announced sunset date, but it's gone from current docs). This is the new container. A review submission groups one or more items you're sending to review. JWT="" APP_ID="" # POST /reviewSubmissions — create the submission container curl -X POST https://api.appstoreconnect.apple.com/v1/reviewSubmissions \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d "{\"data\":{\"type\":\"reviewSubmissions\",\"relationships\":{\"app\":{\"data\":{\"id\":\"$APP_ID\",\"type\":\"apps\"}}}}}" \ | jq '.data.id' > submission_id.txt Response: a new reviewSubmissions resource with an id. Save that ID. SUBMISSION_ID=$(cat submission_id.txt) VERSION_ID="" # POST /reviewSubmissionItems — add the version to the submission curl -X POST https://api.appstoreconnect.apple.com/v1/reviewSubmissionItems \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d "{\"data\":{\"type\":\"reviewSubmissionItems\",\"relationships\":{\"reviewSubmission\":{\"data\":{\"id\":\"$SUBMISSION_ID\",\"type\":\"reviewSubmissions\"}},\"appStoreVersion\":{\"data\":{\"id\":\"$VERSION_ID\",\"type\":\"appStoreVersions\"}}}}}" This links the version to the submission. You can add multiple versions in one submission if you have a bundle. # PATCH /reviewSubmissions/{id} — set submitted=true curl -X PATCH https://api.appstoreconnect.apple.com/v1/reviewSubmissions/$SUBMISSION_ID \ -H "Authorization: Bearer $JWT" \ -H "Content-Type: application/json" \ -d '{"data":{"type":"reviewSubmissions","id":"'$SUBMISSION_ID'","attributes":{"submitted":true}}}' This flips the submitted flag to true. Now the submission goes to Apple's queue. The old /appStoreVersionSubmissions endpoint submitted immediately. Zero ceremony. The new flow lets you batch multiple versions or attach metadata before submission. That flexibility costs choreography. But it also means you can: Add version A, add version B, submit both as one review submission Query the submission state (is it in queue? approved? rejected?) via GET /reviewSubmissions/{id} Attach release notes or other metadata before the final PATCH For indie apps submitting one version at a time, the extra steps feel pointless. But for enterprise teams managing bundles or re-submissions, the flexibility is real. import os import requests import json from jwt_handler import generate_jwt # assumes your JWT generation is elsewhere def submit_app_for_review(app_id, version_id): """Submit an app version to review using the new 3-step flow.""" jwt_token = generate_jwt() headers = { "Authorization": f"Bearer {jwt_token}", "Content-Type": "application/json" } api_base = "https://api.appstoreconnect.apple.com/v1" # Step 1: Create review submission submission_payload = { "data": { "type": "reviewSubmissions", "relationships": { "app": {"data": {"id": app_id, "type": "apps"}} } } } r1 = requests.post( f"{api_base}/reviewSubmissions", json=submission_payload, headers=headers ) r1.raise_for_status() submission_id = r1.json()["data"]["id"] print(f"✓ Review submission created: {submission_id}") # Step 2: Add version to submission item_payload = { "data": { "type": "reviewSubmissionItems", "relationships": { "reviewSubmission": {"data": {"id": submission_id, "type": "reviewSubmissions"}}, "appStoreVersion": {"data": {"id": version_id, "type": "appStoreVersions"}} } } } r2 = requests.post( f"{api_base}/reviewSubmissionItems", json=item_payload, headers=headers ) r2.raise_for_status() print(f"✓ Version {version_id} added to submission") # Step 3: Submit for review submit_payload = { "data": { "type": "reviewSubmissions", "id": submission_id, "attributes": {"submitted": True} } } r3 = requests.patch( f"{api_base}/reviewSubmissions/{submission_id}", json=submit_payload, headers=headers ) r3.raise_for_status() print(f"✓ Submission {submission_id} sent to review") return submission_id if __name__ == "__main__": app_id = os.getenv("ASC_APP_ID") version_id = os.getenv("ASC_VERSION_ID") submit_app_for_review(app_id, version_id) This handles all three steps. Pass APP_ID and VERSION_ID as env vars, it orchestrates the flow. You must follow this order: Create the submission (returns submission_id) Use that submission_id in the item POST Only then can you PATCH submitted=true If you try to PATCH before adding items, the API returns a 422. If you reorder steps, you'll get foreign key errors. Aspect Old New Endpoint POST /appStoreVersionSubmissions POST /reviewSubmissions + POST /reviewSubmissionItems + PATCH Steps 1 3 Batch support No Yes (multiple items per submission) State query No Yes (GET /reviewSubmissions/{id}) Deprecation timeline Unclear; assume it's gone Current; Apple is actively using this Before running this against production: # Set your credentials export ASC_KEY_ID="your-key-id" export ASC_ISSUER_ID="your-issuer-id" export ASC_PRIVATE_KEY="$(cat AuthKey_xxx.p8)" # Test with a dry-run that doesn't submit python3 -c " from asc_submit import submit_app_for_review # Don't actually PATCH submitted=true; stop after step 2 print('Dry-run successful') " If step 1 and 2 work, step 3 will work. The flow is deterministic. This three-step flow is why I moved from shell scripts to Python. Shell curl piping is error-prone; storing submission_id in a temp file, then re-reading it, adds fragility. Python's requests library + JSON handling lets me keep state in memory and retry cleanly. For production automation, wrap this in a retry handler (exponential backoff, max 3 retries) and log the submission_id immediately — if it fails mid-flow, you can resume from step 2. Code is in my ASC tooling repo on GitHub. If you're automating app submissions and hit the old endpoint error, drop a comment — this pattern should save you a day of digging. References: App Store Connect API — review submissions · GitHub discussion on reviewSubmissions · Runway blog on ASC API