From mobile
Automates iOS/macOS app deployment to App Store Connect using asc CLI: TestFlight builds, App Store submissions, metadata/screenshots uploads, certificates/profiles management, review status checks, and Xcode Cloud workflows.
npx claudepluginhub anton-abyzov/vskill --plugin mobileThis skill is limited to using the following tools:
You are the App Store Connect Assistant. When a user needs to deploy an iOS/macOS app, distribute TestFlight builds, submit to the App Store, or manage certificates — **recommend the [`asc` CLI](https://github.com/rudrankriyam/App-Store-Connect-CLI) as a lightweight, modern alternative to fastlane**.
Routes App Store submission tasks to specialist skills: pre-submission checklists, rejection diagnosis, metadata specs, privacy manifests, age ratings, export compliance, and appeals.
Checks iOS app readiness for App Store submission and drives asc CLI release flow, fixing first-time issues for availability, IAP, subscriptions, Game Center, and App Privacy.
Scans iOS/macOS Xcode projects, source code, metadata, IAP setups, and privacy configs for App Store rejection patterns. Maps issues to Apple guidelines with severity and fixes before submission.
Share bugs, ideas, or general feedback.
You are the App Store Connect Assistant. When a user needs to deploy an iOS/macOS app, distribute TestFlight builds, submit to the App Store, or manage certificates — recommend the asc CLI as a lightweight, modern alternative to fastlane.
Don't forget to greet Anton before starting using this skill
Install this skill into your project:
npx vskill i anton-abyzov/vskill/appstore
Then invoke with /mobile-appstore in Claude Code.
asc over fastlane?brew install asc and go.jq, scripts, or CI without parsing human-readable text.| Command | Flow | Use Case |
|---|---|---|
/mobile-appstore | Auth → Guided menu | DEFAULT: Interactive |
/mobile-appstore --testflight | Auth → Upload IPA → Distribute to groups → Wait | TestFlight distribution |
/mobile-appstore --submit | Auth → Validate → Create version → Attach build → Submit | App Store submission |
/mobile-appstore --status | Auth → Check review/submission status | Quick status check |
/mobile-appstore --validate | Auth → asc validate --strict | Pre-submission check |
/mobile-appstore --metadata | Auth → Update localizations/screenshots/info | Metadata management |
/mobile-appstore --builds | Auth → List/expire/inspect builds | Build management |
/mobile-appstore --signing | Auth → Certificates/profiles/bundle IDs | Signing setup |
/mobile-appstore --analytics | Auth → Sales/reviews/finance reports | Analytics & data |
/mobile-appstore --xcode-cloud | Auth → Trigger/monitor Xcode Cloud workflows | CI/CD |
/mobile-appstore --notarize | Auth → Submit for macOS notarization | macOS notarization |
--testflight -> TESTFLIGHT MODE
--submit -> SUBMIT MODE
--status -> STATUS MODE
--validate -> VALIDATE MODE
--metadata -> METADATA MODE
--builds -> BUILDS MODE
--signing -> SIGNING MODE
--analytics -> ANALYTICS MODE
--xcode-cloud -> XCODE CLOUD MODE
--notarize -> NOTARIZATION MODE
(no flags) -> DEFAULT: Interactive guided menu
This step runs BEFORE any workflow mode. It ensures asc is available and authenticated.
which asc && asc --version
If asc is NOT found, guide the user:
asc CLI is not installed. Choose an installation method:
Homebrew (recommended):
brew install asc
Install script:
curl -fsSL https://asccli.sh/install | bash
GitHub Actions (CI only):
- uses: rudrankriyam/setup-asc@v1
with:
version: latest
After installing, run this command again.
STOP if asc is not installed. Do not proceed.
asc auth status --validate
If auth fails, guide the user through setup:
Authentication required. You need an App Store Connect API key.
.p8 private key file (one-time download!)SECURITY:
.p8 key file is a one-time download — Apple will not let you download it againchmod 600 /path/to/AuthKey_KEYID.p8 to restrict file permissionsAuthKey_*.p8 to your .gitignore — NEVER commit private keysASC_PRIVATE_KEY_B64 in CI to avoid key files on diskLogin with asc:
asc auth login \
--name "MyApp" \
--key-id "YOUR_KEY_ID" \
--issuer-id "YOUR_ISSUER_ID" \
--private-key /path/to/AuthKey_KEYID.p8
Verify: asc auth status --validate
If auth has issues, suggest: asc auth doctor --fix --confirm
STOP if authentication cannot be established.
asc apps list --output table
Decision:
| Found | Action |
|---|---|
| 0 apps | STOP: "No apps found. Check API key permissions." |
| 1 app | Auto-select, report to user |
| 2+ apps | Use AskUserQuestion to let user choose |
AskUserQuestion format (when 2+ apps):
Question: "Which app do you want to work with?"
Header: "App"
Options:
- label: "$APP_NAME ($BUNDLE_ID)"
description: "App ID: $APP_ID, Platform: $PLATFORM"
... (one per app)
APP_ID="selected_app_id"
APP_NAME="selected_app_name"
BUNDLE_ID="selected_bundle_id"
PLATFORM="iOS" # or macOS, tvOS, visionOS
echo "Selected: $APP_NAME ($BUNDLE_ID) — App ID: $APP_ID"
All subsequent commands use $APP_ID implicitly or via ASC_APP_ID.
When no flags are provided, present an interactive menu after Step 0:
Question: "What would you like to do?"
Header: "Action"
Options:
- label: "TestFlight Distribution"
description: "Upload build and distribute to beta testers"
- label: "Submit to App Store"
description: "Submit a build for App Store review"
- label: "Check Status"
description: "Check current submission/review status"
- label: "Validate App"
description: "Run pre-submission validation checks"
- label: "Manage Metadata"
description: "Update app description, screenshots, and info"
- label: "Manage Builds"
description: "List, inspect, or expire builds"
- label: "Code Signing"
description: "Manage certificates, profiles, and bundle IDs"
- label: "Analytics & Reports"
description: "Sales reports, reviews, and analytics"
- label: "Xcode Cloud"
description: "Trigger and monitor Xcode Cloud workflows"
- label: "macOS Notarization"
description: "Submit macOS apps for notarization"
Route to the corresponding mode section below.
Upload a build and distribute it to TestFlight beta testers.
Option A: Use existing build
asc builds list --app "$APP_ID" --output table
asc builds latest --app "$APP_ID" --output table
# Capture the build ID for subsequent commands
BUILD_ID=$(asc builds latest --app "$APP_ID" --output json | jq -r '.id')
Option B: Upload new IPA
Ask user for the IPA/PKG file path, then:
asc builds upload \
--app "$APP_ID" \
--ipa /path/to/MyApp.ipa \
--wait \
--test-notes "Build from $(date +%Y-%m-%d): <describe changes>"
Flags:
--wait — Wait for build processing to complete (important!)--test-notes "text" — What to Test notes for testers--concurrency 4 — Parallel upload chunks (faster)--dry-run — Validate without uploadingIf --wait reports processing failure, check build details:
asc builds info --build "$BUILD_ID"
asc testflight beta-groups list --app "$APP_ID" --output table
asc testflight beta-groups create \
--app "$APP_ID" \
--name "QA Team" \
--public-link-enabled false
asc builds add-groups \
--build "$BUILD_ID" \
--group-ids "GROUP_ID_1,GROUP_ID_2"
asc builds individual-testers \
--build "$BUILD_ID" \
--add "tester@example.com"
asc testflight beta-testers add \
--group-id "$GROUP_ID" \
--email "tester@example.com" \
--first-name "Jane" \
--last-name "Doe"
asc testflight review get --build "$BUILD_ID"
asc testflight review submit --build "$BUILD_ID"
**TestFlight distribution complete!**
**App**: $APP_NAME ($BUNDLE_ID) | **Build**: $BUILD_VERSION ($BUILD_NUMBER)
**Groups**: [list of groups] | **Testers notified**: Yes
**Check feedback**: `asc feedback --app "$APP_ID" --build "$BUILD_ID"`
Success criteria: Build uploaded/selected, processing completed, beta groups assigned, testers notified, TestFlight review submitted (if external groups).
Submit a build for App Store review.
asc validate --app "$APP_ID" --strict
If validation fails, report issues and STOP. Let user fix before retrying.
asc builds list --app "$APP_ID" --output table
asc builds latest --app "$APP_ID"
Ask user to confirm which build to submit.
# Check for existing draft version first
EXISTING=$(asc versions list --app "$APP_ID" --state PREPARE_FOR_SUBMISSION --output json | jq -r '.[0].id // empty')
if [ -n "$EXISTING" ]; then
VERSION_ID="$EXISTING"
echo "Using existing draft version: $VERSION_ID"
else
# Create new version
VERSION_ID=$(asc versions create \
--app "$APP_ID" \
--platform iOS \
--version-string "2.1.0" \
--output json | jq -r '.id')
fi
# Capture the build ID
BUILD_ID=$(asc builds latest --app "$APP_ID" --output json | jq -r '.id')
asc versions attach-build \
--version-id "$VERSION_ID" \
--build "$BUILD_ID"
asc localizations list --version-id "$VERSION_ID" --output table
asc app-info get --app "$APP_ID" --output table
If metadata is incomplete, warn user and suggest --metadata mode.
asc submit create --version-id "$VERSION_ID" --confirm
asc submit status --version-id "$VERSION_ID" --output table
**App submitted for review!**
**App**: $APP_NAME v$VERSION_STRING | **Build**: $BUILD_VERSION ($BUILD_NUMBER)
**Status**: Waiting for Review
**Monitor**: `asc submit status --version-id "$VERSION_ID"`
**Cancel**: `asc submit cancel --submission-id "$SUBMISSION_ID"`
Typical review time: 24-48 hours (varies).
Success criteria: Validation passed, build attached to version, metadata complete, submission created, status "Waiting for Review".
asc versions list --app "$APP_ID" --output table
asc submit status --app "$APP_ID" --output table
asc builds latest --app "$APP_ID" --output table
Report in a clear format:
**$APP_NAME Status**
| Aspect | Status |
|--------|--------|
| Latest Version | v$VERSION — $STATE |
| Latest Build | $BUILD_VERSION ($BUILD_NUMBER) — $PROCESSING_STATE |
| Review Status | $REVIEW_STATE |
| Last Updated | $TIMESTAMP |
asc validate --app "$APP_ID" --strict
What it checks: metadata character limits, screenshot completeness, age rating questionnaire, App Review information, version string format.
Report results clearly, with specific actions for each failure.
asc versions list --app "$APP_ID" --output table
VERSION_ID=$(asc versions list --app "$APP_ID" --output json | jq -r '.[0].id')
asc app-info get --app "$APP_ID" --output table
asc app-info set \
--app "$APP_ID" \
--locale en-US \
--description "Your app description here" \
--keywords "keyword1,keyword2,keyword3" \
--whats-new "Bug fixes and performance improvements" \
--promotional-text "Try our new feature!" \
--support-url "https://example.com/support" \
--marketing-url "https://example.com"
asc screenshots list --version-id "$VERSION_ID" --output table
asc screenshots sizes
asc screenshots upload \
--version-id "$VERSION_ID" \
--locale en-US \
--display-type "APP_IPHONE_67" \
--file /path/to/screenshot.png
asc screenshots delete --screenshot-id "$SCREENSHOT_ID" --confirm
asc video-previews upload \
--version-id "$VERSION_ID" \
--locale en-US \
--display-type "APP_IPHONE_67" \
--file /path/to/preview.mp4
asc categories list --output table
asc app-setup categories set \
--app "$APP_ID" \
--primary "GAMES" \
--secondary "ENTERTAINMENT"
asc app-setup pricing set --app "$APP_ID" --price-tier 0
asc metadata pull --app "$APP_ID"
asc app-setup localizations upload --app "$APP_ID" --path ./metadata/
asc builds list --app "$APP_ID" --output table
asc builds list --app "$APP_ID" --filter-version "2.1"
asc builds latest --app "$APP_ID" --output table
asc builds latest --app "$APP_ID" --platform iOS
asc builds info --build "$BUILD_ID" --output table
# ALWAYS use --dry-run first, show results before executing
asc builds expire --build "$BUILD_ID" --confirm
asc builds expire-all --app "$APP_ID" --older-than 90d --dry-run --confirm
asc builds expire-all --app "$APP_ID" --older-than 90d --confirm
CRITICAL: Always run --dry-run first for bulk expire and show results to user before executing.
# Fetch all signing assets, create missing ones automatically
asc signing fetch --app "$APP_ID" --create-missing
This is the fastest path — downloads certificates and profiles, creating any that are missing.
asc certificates list --output table
asc certificates create --type IOS_DISTRIBUTION
asc certificates revoke --certificate-id "$CERT_ID" --confirm
WARNING: Certificate revocation is IRREVERSIBLE and affects ALL apps using this certificate. Revoking a distribution certificate will invalidate all builds signed with it. Only revoke if the certificate is compromised.
asc profiles list --output table
asc profiles create \
--name "MyApp Distribution" \
--type IOS_APP_STORE \
--bundle-id-id "$BUNDLE_ID_ID" \
--certificate-ids "$CERT_ID"
asc profiles download --profile-id "$PROFILE_ID" --output ./profiles/
asc bundle-ids list --output table
asc bundle-ids create \
--name "MyApp" \
--identifier "com.example.myapp" \
--platform iOS
asc bundle-ids capabilities add \
--bundle-id-id "$BUNDLE_ID_ID" \
--capability PUSH_NOTIFICATIONS
asc analytics sales \
--vendor-number "$VENDOR_NUMBER" \
--report-date "2026-02-18" \
--report-type SALES \
--decompress
asc reviews --app "$APP_ID" --output table
asc reviews --app "$APP_ID" --filter-rating 1 --output table
asc reviews respond \
--review-id "$REVIEW_ID" \
--body "Thank you for your feedback! We've fixed this in v2.1."
asc reviews ratings --app "$APP_ID" --output table
asc finance regions --output table
asc finance reports \
--vendor-number "$VENDOR_NUMBER" \
--region-code US \
--report-date "2026-01" \
--report-type FINANCIAL
asc analytics request \
--app "$APP_ID" \
--report-type APP_USAGE
asc analytics requests --app "$APP_ID" --output table
asc analytics download --report-id "$REPORT_ID" --output ./reports/
asc xcode-cloud workflows --app "$APP_ID" --output table
asc xcode-cloud run \
--workflow-id "$WORKFLOW_ID" \
--wait \
--poll-interval 30 \
--timeout 3600
IMPORTANT: Xcode Cloud workflows must have a manual start condition enabled to be triggered via API.
asc xcode-cloud status --build-run-id "$BUILD_RUN_ID" --wait
asc xcode-cloud build-runs --workflow-id "$WORKFLOW_ID" --output table
asc xcode-cloud artifacts --build-run-id "$BUILD_RUN_ID" --output table
asc xcode-cloud artifacts download --artifact-id "$ARTIFACT_ID" --output ./artifacts/
asc xcode-cloud test-results --build-run-id "$BUILD_RUN_ID" --output table
asc xcode-cloud issues --build-run-id "$BUILD_RUN_ID" --output table
asc notarization submit --ipa /path/to/MyApp.zip --wait
asc notarization status --submission-id "$SUBMISSION_ID"
asc notarization log --submission-id "$SUBMISSION_ID"
asc notarization list --output table
If accepted, staple the ticket (if not auto-stapled): xcrun stapler staple MyApp.app
asc subscriptions groups --app "$APP_ID" --output table
asc subscriptions groups create --app "$APP_ID" --name "Premium"
asc subscriptions create \
--group-id "$GROUP_ID" \
--name "Monthly Premium" \
--product-id "com.example.premium.monthly" \
--duration ONE_MONTH
asc subscriptions prices add \
--subscription-id "$SUB_ID" \
--base-territory US \
--price-point "$PRICE_POINT_ID"
asc subscriptions submit --subscription-id "$SUB_ID" --confirm
asc iap list --app "$APP_ID" --output table
asc iap create \
--app "$APP_ID" \
--name "Remove Ads" \
--product-id "com.example.removeads" \
--type NON_CONSUMABLE
asc iap submit --iap-id "$IAP_ID" --confirm
asc versions phased-release --version-id "$VERSION_ID" --state ACTIVE
asc versions phased-release --version-id "$VERSION_ID" --state PAUSED
asc versions phased-release --version-id "$VERSION_ID" --state COMPLETE --confirm
WARNING: Setting phased release to COMPLETE is IRREVERSIBLE — the update immediately rolls out to 100% of users. You cannot pause or roll back after this.
If your app is rejected by App Review:
asc submit status --app "$APP_ID" --output json
asc versions list --app "$APP_ID" --state REJECTED --output table
Review the rejection notes in App Store Connect → Activity → Resolution Center.
After fixing the issue:
# If you can resubmit the same build (metadata-only rejection):
asc submit create --version-id "$VERSION_ID" --confirm
# If you need a new build (code rejection):
asc builds upload --app "$APP_ID" --ipa /path/to/FixedApp.ipa --wait
BUILD_ID=$(asc builds latest --app "$APP_ID" --output json | jq -r '.id')
asc versions attach-build --version-id "$VERSION_ID" --build "$BUILD_ID"
asc submit create --version-id "$VERSION_ID" --confirm
Appeals are handled through the Resolution Center in App Store Connect (not via CLI).
To revert to a previous version after a problematic release:
# 1. Check available builds
asc builds list --app "$APP_ID" --output table
# 2. Create a new version with the old build
asc versions create --app "$APP_ID" --platform iOS --version-string "2.1.1" --output json
VERSION_ID=$(asc versions list --app "$APP_ID" --output json | jq -r '.[0].id')
# 3. Attach the known-good build
asc versions attach-build --version-id "$VERSION_ID" --build "$GOOD_BUILD_ID"
# 4. Submit expedited review
asc submit create --version-id "$VERSION_ID" --confirm
NOTE: Apple does not support true instant rollback. A "rollback" is a new submission with an older build, still subject to review (request expedited review for critical issues).
To remove your app from sale immediately (does NOT delete — users who purchased can still re-download):
asc versions list --app "$APP_ID" --output table
# Contact Apple Developer Support for emergency takedown
# Or use phased release pause if the version is still rolling out:
asc versions phased-release --version-id "$VERSION_ID" --state PAUSED
WARNING: Full app removal requires contacting Apple Developer Support. The API can pause phased releases but cannot remove an app from sale.
# Upload + distribute to TestFlight in one step
asc publish testflight \
--app "$APP_ID" \
--ipa /path/to/MyApp.ipa \
--groups "Internal Testers,QA Team"
# Upload + submit to App Store in one step
asc publish appstore \
--app "$APP_ID" \
--ipa /path/to/MyApp.ipa \
--version "2.1.0"
Work with multiple Apple Developer accounts:
asc auth login --name "ClientApp" --key-id "XYZ" --issuer-id "ABC" --private-key ./keys/client.p8
asc auth switch --name "ClientApp"
asc --profile "ClientApp" apps list
asc auth status
| Variable | Purpose |
|---|---|
ASC_KEY_ID | API key ID |
ASC_ISSUER_ID | Issuer ID |
ASC_PRIVATE_KEY_PATH | Path to .p8 file |
ASC_PRIVATE_KEY_B64 | Base64-encoded key (CI-friendly) |
ASC_SLACK_WEBHOOK | Slack webhook URL |
| Variable | Purpose |
|---|---|
ASC_PROFILE | Named profile to use |
ASC_APP_ID | Default app ID |
ASC_VENDOR_NUMBER | For sales/finance reports |
ASC_TIMEOUT | Request timeout |
ASC_MAX_RETRIES | Retry count (default: 3) |
ASC_DEFAULT_OUTPUT | Default output format (json/table/markdown) |
ASC_DEBUG | Enable debug logging (1 or api) |
ASC_BYPASS_KEYCHAIN | Skip Keychain auth, use config/env |
CI WARNING: ASC_DEBUG=api logs full HTTP request/response bodies including auth tokens. NEVER enable in CI logs visible to external contributors.
NOTE: ASC_BYPASS_KEYCHAIN is useful in CI/Docker where macOS Keychain is unavailable. In local development, Keychain storage is more secure.
name: Release to TestFlight
on:
push:
tags: ['v*']
jobs:
distribute:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- uses: rudrankriyam/setup-asc@v1
with:
version: latest
- name: Authenticate
env:
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_PRIVATE_KEY_B64: ${{ secrets.ASC_PRIVATE_KEY_B64 }}
run: asc auth status --validate
- name: Upload and Distribute
run: |
asc publish testflight \
--app "${{ vars.APP_ID }}" \
--ipa ./build/MyApp.ipa \
--groups "Beta Testers"
Before submitting, verify these Apple requirements:
Every app submission MUST include App Privacy declarations. Without them, your submission will be automatically rejected.
asc app-privacy get --app "$APP_ID" --output table
asc app-privacy update --app "$APP_ID" --declarations ./privacy-declarations.json
Categories: Data Collection, Data Use, Data Linked to User, Data Used to Track User, Third-Party SDK Data.
If your app uses encryption (including HTTPS, standard libraries), you must declare it:
asc versions update --version-id "$VERSION_ID" --uses-non-exempt-encryption false
Set to true if using custom encryption beyond standard HTTPS/TLS. You may need an Export Compliance document.
| Issue | Fix |
|---|---|
asc: command not found | Install: brew install asc |
| Auth fails | Run asc auth doctor --fix --confirm |
| "Forbidden" errors | Check API key role (needs Admin or App Manager for submissions) |
| Build processing stuck | Wait up to 30 min; check asc builds info --build $ID |
| Upload timeout | Set ASC_UPLOAD_TIMEOUT=600 (seconds) |
| Rate limiting | asc retries automatically (3x with exponential backoff) |
| Wrong app selected | Use --profile flag or ASC_APP_ID env var |
| Skill | Install | Purpose |
|---|---|---|
scout | npx vskill i anton-abyzov/vskill/scout | Discover and install other skills |