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

  1. Go to App Store Connect

  2. Navigate to Users and Access → Integrations → App Store Connect API

  3. Click Generate API Key (or use an existing one)

  4. Download the .p8 private key file — Apple only lets you download it once

  5. Note 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)

  1. In the target app’s repo, open the Xcode workspace:

    cd peek_field_app/ios/App
    open App.xcworkspace
    
  2. In Xcode: Integrate → Create Workflow

  3. Click through all the Next prompts and set the app name

  4. 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 → productWorkflowsworkflowEditEnvironment → 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_PROJECT_ID

GitLab numeric project ID (e.g. 3372)

No

GITLAB_ACCESS_TOKEN

Project access token created by the wizard

Yes

Step 5 — Add post-actions

Under Workflows → Post-Actions → Add:

  1. TestFlight Internal Testing - iOS

    • Artifact: Archive - iOS

    • Groups: select or create the relevant internal tester group

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

31ce63a2-a54f-3e25-b883-dc13290abb7f

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
    }
  ]
}