Thought I'd share my solution here for anyone experiencing the same issues and have experience with Python. I made some simple scripts that can be used to manually extract out the download URLs of each artifact.
# generate_token.py
import jwt
import time
from datetime import datetime, timedelta
# Define your App Store Connect API credentials
KEY_ID = '________'
ISSUER_ID = '________'
PRIVATE_KEY_PATH = '________.p8'
with open(PRIVATE_KEY_PATH, 'r') as key_file:
private_key = key_file.read()
def generate_token():
headers = {
"alg": "ES256",
"kid": KEY_ID,
"typ": "JWT"
}
payload = {
"iss": ISSUER_ID,
"exp": int(time.time()) + 20 * 60, # Token expiration in 20 minutes
"aud": "appstoreconnect-v1"
}
token = jwt.encode(payload, private_key, algorithm="ES256", headers=headers)
return token
# fetch_workflows.py
import time
import requests
from datetime import datetime, timedelta
def fetch_products(token):
url = f"https://api.appstoreconnect.apple.com/v1/ciProducts"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
def fetch_workflows(product_id, token):
url = f"https://api.appstoreconnect.apple.com/v1/ciProducts/{product_id}/workflows"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
if __name__ == "__main__":
from generate_token import generate_token
products = fetch_products(generate_token())
print(f"Found {len(products)} products")
for product in products:
print(f"{product['attributes']['name']} ({product['attributes']['productType']}): {product['id']}")
workflows = fetch_workflows(product['id'], generate_token())
print(f"Found {len(workflows)} workflows")
for workflow in workflows:
print(f"{workflow['attributes']['name']}: {workflow['id']}")
# fetch_build_runs.py
import time
import requests
from datetime import datetime, timedelta
# Fetch all build runs for the specified workflow
def fetch_build_runs(workflow_id, token):
url = f"https://api.appstoreconnect.apple.com/v1/ciWorkflows/{workflow_id}/buildRuns"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
params = {
"limit": 200 # Adjust the limit as needed; 200 is the maximum
}
response = requests.get(url, headers=headers, params=params)
response.raise_for_status()
return response.json().get("data", [])
if __name__ == "__main__":
import argparse
from generate_token import generate_token
parser = argparse.ArgumentParser(description="Fetch all build runs for a given workflow ID.")
parser.add_argument("workflow_id", help="The workflow ID for which to fetch build runs for.")
args = parser.parse_args()
build_runs = fetch_build_runs(args.workflow_id, generate_token())
print(f"Found {len(build_runs)} runs for workflow {args.workflow_id}")
for build_run in build_runs:
print(f"Build {build_run['attributes']['number']}: {build_run['id']}")
# fetch_build_actions.py
import time
import requests
from datetime import datetime, timedelta
def fetch_build_actions(build_run_id, token):
url = f"https://api.appstoreconnect.apple.com/v1/ciBuildRuns/{build_run_id}/actions"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
if __name__ == "__main__":
import argparse
from generate_token import generate_token
parser = argparse.ArgumentParser(description="Fetch all build actions for a given build run ID.")
parser.add_argument("build_run_id", help="The build run ID for which to fetch build actions from")
args = parser.parse_args()
build_actions = fetch_build_actions(args.build_run_id, generate_token())
print(f"Found {len(build_actions)} actions for run {args.build_run_id}")
for build_action in build_actions:
print(f"{build_action['attributes']['name']}: {build_action['id']}")
# fetch_artifacts.py
import time
import requests
from datetime import datetime, timedelta
def fetch_artifacts(build_action_id, token):
url = f"https://api.appstoreconnect.apple.com/v1/ciBuildActions/{build_action_id}/artifacts"
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
response = requests.get(url, headers=headers)
response.raise_for_status()
return response.json().get("data", [])
if __name__ == "__main__":
import argparse
from generate_token import generate_token
parser = argparse.ArgumentParser(description="Fetch all artifacts for a given build action ID.")
parser.add_argument("build_action_id", help="The build action ID for which to fetch artifacts.")
args = parser.parse_args()
artifacts = fetch_artifacts(args.build_action_id, generate_token())
print(f"Found {len(artifacts)} artifacts for action {args.build_action_id}")
for artifact in artifacts:
print(f"{artifact['attributes']['fileName']}: {artifact['attributes']['downloadUrl']}")
Note that you will need to pip install cryptography pyjwt requests
.