Xcode Cloud API Configuration Tool¶
This tool allows you to programmatically export and create Xcode Cloud workflow configurations using the App Store Connect REST API.
The script lives at ~/dev-peek/dev-peek-workspace/workspace-common/ and
uses Python 3.12 with Twisted.
Getting API Credentials¶
You need an App Store Connect API key to authenticate.
Step 1 — Create the API key
Go to App Store Connect
Navigate to Users and Access → Integrations → App Store Connect API
Click Generate API Key (or use an existing one)
Download the
.p8private key file — Apple only lets you download it onceNote your Key ID (shown in the table, also in the filename:
AuthKey_<KEY_ID>.p8)
Step 2 — Get the Issuer ID
The Issuer ID is shown at the top of the same
Users and Access → Integrations → App Store Connect API page.
It is a UUID like 69a6de79-d6e1-47e3-e053-5b8c7c11a4d1.
Step 3 — Set environment variables
export ASC_KEY_ID="966L93C63C"
export ASC_ISSUER_ID="69a6de79-d6e1-47e3-e053-5b8c7c11a4d1"
export ASC_PRIVATE_KEY_PATH="/path/to/AuthKey_966L93C63C.p8"
Setup¶
Create a virtual environment using the Peek Python installation:
cd ~/dev-peek/dev-peek-workspace/workspace-common
~/dev-peek/opt/python/bin/python -m venv .venv
.venv/bin/pip install twisted click "PyJWT[crypto]" pyopenssl service_identity certifi
Interactive Setup Wizard¶
The wizard walks through adding a new app by copying an existing workflow. It handles exporting the source workflow, updating the new product’s default workflow, creating a GitLab access token, and printing the manual steps that cannot be done via API.
cd ~/dev-peek/dev-peek-workspace/workspace-common
export ASC_KEY_ID="..." ASC_ISSUER_ID="..." ASC_PRIVATE_KEY_PATH="..."
.venv/bin/python bin/pd_setup_xcode_cloud_app.py
Usage — Individual Commands¶
These commands are available from the xcode-cloud CLI if the package is
installed from ~/Downloads/xcode-cloud, or by calling the modules directly.
List all Xcode Cloud products (apps):
.venv/bin/xcode-cloud list-products
List workflows for a product:
.venv/bin/xcode-cloud list-workflows <product-id>
Export all workflows for a product to JSON files:
.venv/bin/xcode-cloud export-workflows <product-id> -o ./exported
List available Xcode and macOS versions:
.venv/bin/xcode-cloud list-xcode-versions
.venv/bin/xcode-cloud list-macos-versions
List connected repositories:
.venv/bin/xcode-cloud list-repositories
List registered bundle IDs:
.venv/bin/xcode-cloud list-bundle-ids
Create a workflow from an exported JSON file:
.venv/bin/xcode-cloud create-workflow <product-id> ./exported/<workflow>.json \
--xcode-version-id <xcode-version-id> \
--macos-version-id <macos-version-id>
Update an existing workflow from a JSON file:
.venv/bin/xcode-cloud update-workflow <workflow-id> ./exported/<workflow>.json \
--xcode-version-id <xcode-version-id> \
--macos-version-id <macos-version-id>
Adding a New App¶
Apple’s public API requires a GitLab project bot to be present in the repository before a product can be registered with Xcode Cloud. This bot is created automatically by Apple’s servers during the initial Xcode setup flow — it cannot be triggered via the public API. This one step must be done manually; everything after it is fully API-driven.
Step 1 — One-time Xcode setup (creates the GitLab bot and product)
In the target app’s repo, open the Xcode workspace:
cd peek_field_app/ios/App open App.xcworkspace
In Xcode: Integrate → Create Workflow
Click through all the Next prompts and set the app name
On the final screen, choose Don’t Start Build — the default workflow Xcode creates will be replaced in the next step
This registers the GitLab repo with Xcode Cloud (creating a project-scoped
bot account), creates the bundle ID, and creates an initial ciProduct.
Step 2 — Run the interactive setup wizard
cd ~/dev-peek/dev-peek-workspace/workspace-common
.venv/bin/python bin/pd_setup_xcode_cloud_app.py
The wizard will:
Copy the workflow configuration from an existing product
Update the new product’s default workflow
Create a GitLab project access token (reads
~/.gitlabtoken)Print the environment variables to add in App Store Connect
Prompt for the TestFlight and Slack post-action steps
Step 3 — Add outbound network access to the workflow
Xcode Cloud sandboxes outbound network connections from custom build scripts.
The CI scripts upload build artefacts to gitlab.synerty.com, so that host
must be explicitly allowed in the workflow.
In App Store Connect → product → Workflows → workflow → Edit → Environment → scroll to Custom Build Script Network Access → add:
gitlab.synerty.com
Without this, ci_post_xcodebuild.sh will fail with
Could not resolve host: gitlab.synerty.com (curl exit code 6).
Note
This setting is not exposed in the App Store Connect API, so the setup wizard cannot configure it automatically. It must be added manually once per workflow.
Step 4 — Add environment variables in App Store Connect
The wizard prints the values — add them under Workflows → Environment:
Name |
Value |
Secret |
|---|---|---|
|
GitLab numeric project ID (e.g. |
No |
|
Project access token created by the wizard |
Yes |
Step 5 — Add post-actions
Under Workflows → Post-Actions → Add:
TestFlight Internal Testing - iOS
Artifact: Archive - iOS
Groups: select or create the relevant internal tester group
Notify - Successes and Failures
Slack: select the relevant channel
Xcode and macOS Version IDs¶
The Xcode and macOS version resource IDs are paired — the same UUID is used
for both --xcode-version-id and --macos-version-id.
UUID |
Xcode |
macOS |
|---|---|---|
|
Xcode 17E202 |
macOS 25E246 |
To find current IDs:
.venv/bin/xcode-cloud list-xcode-versions
.venv/bin/xcode-cloud list-macos-versions
Exported Workflow JSON Schema¶
Exported JSON files can be edited before re-importing. Fields:
{
"name": "Default",
"description": "",
"isEnabled": true,
"clean": false,
"containerFilePath": "peek_field_app/ios/App/App.xcworkspace",
"actions": [
{
"name": "Archive - iOS",
"actionType": "ARCHIVE",
"destination": null,
"buildDistributionAudience": "APP_STORE_ELIGIBLE",
"testConfiguration": null,
"scheme": "App",
"platform": "IOS",
"isRequiredToPass": true
}
]
}