The first question at any Xcode Cloud demo in a mixed-stack company is always the same: "Our backend is Kotlin, we have an Android team, and everything runs in GitHub Actions. Does all of that move there too?" The short answer: no. The long answer is this article: Xcode Cloud doesn't replace a shared pipeline, but it slots into one quite well as a single-role worker — if you know the three bridges: App Store Connect API in, webhooks out, artifacts on demand.
The service basics — workflows, pricing, pros and cons — are in the first article of this series. This one is integration only.
Boundaries: what it builds and what it doesn't#
The Xcode Cloud environment is macOS on Apple silicon. No Linux, no Docker, no bring-your-own runners. You can't build an Android app there — not because it's forbidden, but because the environment for it doesn't exist: no Android SDK, no containers to smuggle one in. The backend, even more so.
So the layout for a typical company looks like this: backend and Android stay where they live — GitHub Actions, GitLab CI, Jenkins. Xcode Cloud takes the iOS/macOS part: build, tests, signing, TestFlight. The job reduces to making the two systems see each other.
Inbound: starting a build from outside#
The App Store Connect API can drive Xcode Cloud end to end: read products and workflows, inspect results, and — the key part — start builds: POST /v1/ciBuildRuns.
A real scenario: the backend just deployed a new API contract version to staging, and you want the iOS app to run its integration tests against that staging right away. The trigger script is plain Python; the only dependencies are pyjwt and requests:
"""trigger_xcode_cloud.py — start a workflow via the App Store Connect API."""
import os
import time
import jwt
import requests
ISSUER_ID = os.environ["ASC_ISSUER_ID"] # App Store Connect → Users and Access → Integrations
KEY_ID = os.environ["ASC_KEY_ID"]
PRIVATE_KEY = os.environ["ASC_PRIVATE_KEY"] # contents of AuthKey_XXXX.p8
WORKFLOW_ID = os.environ["XC_WORKFLOW_ID"] # GET /v1/ciProducts/{id}/workflows — once, by hand
token = jwt.encode(
{"iss": ISSUER_ID, "aud": "appstoreconnect-v1", "exp": int(time.time()) + 600},
PRIVATE_KEY,
algorithm="ES256",
headers={"kid": KEY_ID},
)
resp = requests.post(
"https://api.appstoreconnect.apple.com/v1/ciBuildRuns",
json={"data": {
"type": "ciBuildRuns",
"relationships": {
"workflow": {"data": {"type": "ciWorkflows", "id": WORKFLOW_ID}},
},
}},
headers={"Authorization": f"Bearer {token}"},
timeout=30,
)
resp.raise_for_status()
print("Started build run:", resp.json()["data"]["id"])The token is an ES256 JWT with a lifetime of up to 20 minutes; the key is issued once in App Store Connect, and the Developer role is enough to start builds. The easiest way to get WORKFLOW_ID is a one-off GET request, then hardcode it into your secrets.
In GitHub Actions this becomes a cheap Linux job at the very end of the staging deploy:
ios-contract-tests:
needs: deploy-staging
runs-on: ubuntu-latest # Linux at $0.008/min — we only poke an API
steps:
- uses: actions/checkout@v4
- run: pip install "pyjwt[crypto]" requests
- run: python ci/trigger_xcode_cloud.py
env:
ASC_ISSUER_ID: ${{ secrets.ASC_ISSUER_ID }}
ASC_KEY_ID: ${{ secrets.ASC_KEY_ID }}
ASC_PRIVATE_KEY: ${{ secrets.ASC_PRIVATE_KEY }}
XC_WORKFLOW_ID: ${{ secrets.XC_WORKFLOW_ID }}Note that Xcode Cloud builds what's in the git repository, not what you sent it: the build runs from the HEAD of the branch the workflow is configured for. Unpushed changes won't build. For Jenkins there's an official xcode-cloud-for-pipeline plugin working on the same principle.
Outbound: webhooks back into the pipeline#
The reverse direction is covered by webhooks: you set a URL in the workflow settings, and Xcode Cloud POSTs when a build run is created and when it completes — with a JSON description of the build, its status, and links. A Slack integration ships out of the box, so if channel notifications are all you need, you can skip writing a webhook server entirely.
If the shared pipeline actually has to wait for the iOS build result (say, to assemble a release train of backend, Android and iOS at one version), there are two options. First: receive the webhook on your own endpoint and continue the pipeline from there. Apple's documentation doesn't describe request signing, so keep the endpoint on a secret path and re-verify the incoming build id with a GET back to the API — don't trust the webhook body. Second: the dumb, reliable one — after POST /v1/ciBuildRuns, poll GET /v1/ciBuildRuns/{id} once a minute until it reports completed. Against the length of the build itself, polling spoils nothing.
Artifacts and statuses#
Inside a GitHub repository, Xcode Cloud posts pull request statuses on its own, and you can make them required for merging — that part works with zero code.
Artifacts come through the same API: a build run has build actions, and those have artifacts with download URLs. That's your .ipa, .xcresult and logs. A typical nightly setup: scheduled workflow → completion webhook → your job grabs the .ipa and .xcresult and files them into the corporate archive where QA and security can see them.
Monorepos and shared contracts#
Workflow start conditions can filter by files and folders: if the iOS app lives in ios/ of a monorepo, builds trigger only on changes to that folder — the Android team's commits won't burn your hours.
Code shared with the backend — protobuf schemas, OpenAPI contracts — is generated in ci_scripts/ci_post_clone.sh: install the generator via Homebrew, run it, and the build proceeds with up-to-date types. Environment variables can be shared across several workflows, so the staging URL doesn't spread by copy-paste.
What stays crooked#
Three things integration doesn't cure. Integration tests only reach an external staging — you can't spin up a docker-compose with mocks next to the build. Workflow configuration lives in App Store Connect, not in git: you can export it through the API, but "infrastructure as code" here is your script, not a platform feature. And the build schedule ends up smeared across two systems, so "what builds when" can no longer be answered from one repository — make a wiki page about it, seriously.
The decision checklist#
The "shared CI + Xcode Cloud" hybrid makes sense if most of these hold:
- The product is multi-platform, but what the iOS team actually suffers from is signing and Mac agents.
- The shared pipeline already lives in GitHub Actions / GitLab / Jenkins, and nobody will let you throw it away.
- Integration tests are fine talking to a staging environment over the network.
- Storing iOS build secrets in App Store Connect is acceptable.
- Build volume fits into 25–250 hours per month.
If it adds up — start small: one workflow for PR checks on the iOS folder, statuses in GitHub, Slack notifications. Build the API and webhook bridges when the release train genuinely needs to wait for the iOS build — not because the architecture diagram looks incomplete without them.



