Fetching latest headlines…
The ASC API's 3-step review submission flow (and why appStoreVersionSubmissions is gone)
NORTH AMERICA
🇺🇸 United StatesMay 7, 2026

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

0 views0 likes0 comments
Originally published byDev.to

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.

The old way (dead)

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":"<versionId>","type":"appStoreVersions"}}}}}'

This worked. One endpoint, one request, done. But Apple deprecated it (no announced sunset date, but it's gone from current docs).

The new way (step-by-step)

Step 1: Create a review submission

This is the new container. A review submission groups one or more items you're sending to review.

JWT="<your-jwt>"
APP_ID="<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.

Step 2: Add the app version to the submission

SUBMISSION_ID=$(cat submission_id.txt)
VERSION_ID="<your-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.

Step 3: Submit for review

# 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.

Why three steps?

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.

Python script that actually works

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.

The pitfall: order matters

You must follow this order:

  1. Create the submission (returns submission_id)
  2. Use that submission_id in the item POST
  3. 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.

What changed from old to new

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

Testing the flow locally

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.

Automation insight

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

Comments (0)

Sign in to join the discussion

Be the first to comment!