AUTOMATIONFeb 24, 2026 // 12 min read // Written by Founders

AUTOMATING TESTFLIGHT DISPATCHES USING THE APP STORE CONNECT API

Uploading iOS applications to TestFlight manually through Xcode Organizer is a slow, error-prone chore that breaks CI/CD flow. You build the IPA, open Xcode, wait for archive validation, click through dialogs, and then wait again for processing. A single upload can consume 15 minutes of developer attention.

By automating the entire pipeline using the App Store Connect API, you can dispatch builds directly from your terminal or CI runner — no GUI interaction required. At Venelx, every successful iOS compilation automatically triggers a TestFlight upload and beta group assignment.

Generate an App Store Connect API Key

Navigate to App Store ConnectUsers and AccessKeysApp Store Connect API. Generate a new key with the Developer role (or Admin if you need to manage app metadata).

Download the .p8 key file immediately — Apple only allows you to download it once. Record three values:

  • Key ID (e.g., A1B2C3D4E5)
  • Issuer ID (e.g., 69a6de78-...)
  • Private Key File (the .p8 file)

Store these values securely in your CI environment vault, never in source control.

Generate a JWT Token for Authentication

The App Store Connect API requires a short-lived JSON Web Token (JWT) for every request. The token is signed with your .p8 private key using the ES256 algorithm:

import time
import jwt  # PyJWT package

def generate_apple_jwt(key_id: str, issuer_id: str, p8_filepath: str) -> str:
    with open(p8_filepath, "r") as f:
        private_key = f.read()
    
    headers = {
        "alg": "ES256",
        "kid": key_id,
        "typ": "JWT"
    }
    
    payload = {
        "iss": issuer_id,
        "exp": int(time.time()) + 1200,  # 20 minute expiry
        "aud": "appstoreconnect-v1"
    }
    
    return jwt.encode(payload, private_key, algorithm="ES256", headers=headers)

# Usage
token = generate_apple_jwt("A1B2C3D4E5", "69a6de78-...", "./keys/AuthKey.p8")

Token Security Best Practices

  • Short Expiry: Always set tokens to expire within 20 minutes. Apple rejects tokens with expiry windows longer than 20 minutes.
  • Never Cache Tokens: Generate a fresh JWT for each API call to prevent replay attacks.
  • Rotate Keys Quarterly: Revoke and regenerate API keys on a regular schedule.

Upload the IPA Using Apple Transporter

Apple provides two command-line tools for uploading compiled applications:

Method 1: altool (Legacy, Still Supported)

# Upload IPA using xcrun altool
xcrun altool --upload-app \
  -f ./build/app-release.ipa \
  -t ios \
  --apiKey "$APPLE_API_KEY_ID" \
  --apiIssuer "$APPLE_ISSUER_ID"

Method 2: notarytool + iTMSTransporter (Recommended)

# Upload using Apple's Transporter (faster, supports resumable uploads)
xcrun iTMSTransporter -m upload \
  -f ./build/app-release.ipa \
  -apiKey "$APPLE_API_KEY_ID" \
  -apiIssuer "$APPLE_ISSUER_ID" \
  -verbose eXtreme

Automate Beta Group Assignment via API

After the build processes on Apple's servers (typically 10-30 minutes), you can programmatically assign it to a TestFlight beta group:

# Fetch the latest build ID
BUILD_ID=$(curl -s -H "Authorization: Bearer $JWT_TOKEN" \
  "https://api.appstoreconnect.apple.com/v1/builds?filter[app]=$APP_ID&sort=-uploadedDate&limit=1" \
  | jq -r '.data[0].id')

# Assign build to beta testers group
curl -X POST \
  "https://api.appstoreconnect.apple.com/v1/betaGroups/$BETA_GROUP_ID/relationships/builds" \
  -H "Authorization: Bearer $JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"data\":[{\"type\":\"builds\",\"id\":\"$BUILD_ID\"}]}"

Complete CI Pipeline: Build → Upload → Notify

Combining all the steps into a single CI script:

#!/bin/bash
set -eo pipefail

echo "=== Step 1: Build Release IPA ==="
xcodebuild -workspace App.xcworkspace \
  -scheme "Release" \
  -sdk iphoneos \
  -configuration Release \
  -archivePath build/App.xcarchive \
  archive

xcodebuild -exportArchive \
  -archivePath build/App.xcarchive \
  -exportOptionsPlist ExportOptions.plist \
  -exportPath build/

echo "=== Step 2: Upload to TestFlight ==="
xcrun altool --upload-app \
  -f build/App.ipa -t ios \
  --apiKey "$APPLE_API_KEY_ID" \
  --apiIssuer "$APPLE_ISSUER_ID"

echo "=== Step 3: Notify Team ==="
curl -X POST "$SLACK_WEBHOOK_URL" \
  -H "Content-Type: application/json" \
  -d '{"text":"✅ New TestFlight build uploaded successfully!"}'

Running these dispatches on dedicated M4 runners ensures your builds compile, upload, and arrive in TestFlight in under 5 minutes total. Read more about credential management in Securing Fastlane secrets.

References & Citations

← BACK TO ARTICLES