Documentation
Push an .ipa or .apk from CI, share one link, and your testers tap install. Most of what follows is about the platform rules that decide whether that install actually succeeds.
Buildchute has three moving parts. Everything in these docs is one of them or a rule about them.
| Concept | What it is |
|---|---|
| App | One mobile app. Holds a bundle ID and its builds. |
| Build | One uploaded binary, with the version, size, icon and signing data we read out of it. |
| Channel | A named stream inside an app — staging, beta, release. Testers get a channel link, not a build link. |
Builds are stored privately. Nothing gets a public URL — every download is a short-lived signed link minted when someone with access asks for one.
Create an app in the dashboard and copy its upload token. Treat it like a deploy key — it can upload builds to that app and nothing else.
export BUILDCHUTE_TOKEN=bc_live_... npx buildchute upload ./build/Acme.ipa --channel staging
The upload is resumable. A dropped connection or a killed CI runner resumes from where it stopped rather than restarting a 1.4 GB transfer.
The command prints an install link. Send it to a tester, paste it in Slack, or point QA at the channel's /latest link so it always resolves to your newest build.
✓ Uploaded Acme 1.4.2 (2891) — 48.2 MB in 6s https://buildchute.com/b/a8f3k https://buildchute.com/acme/staging/latest
The CLI is the primary interface. It needs no install step — npx fetches it, and CI images cache it between runs.
| Command | What it does |
|---|---|
| buildchute upload <file> | Upload an .ipa or .apk and print its install link. |
| buildchute list | List recent builds for the current app. |
| buildchute open <id> | Open a build's install page in your browser. |
| buildchute revoke <id> | Kill a build's links immediately. |
| buildchute whoami | Show which app and workspace the current token belongs to. |
| Flag | Effect |
|---|---|
| --channel <name> | Which channel to publish to. Defaults to staging. |
| --notes <text> | Release notes shown to testers. Accepts a file with @notes.md. |
| --expires <duration> | Link lifetime, e.g. 7d or 48h. Defaults to the plan's max. |
| --password <value> | Require a password before the build is shown. |
| --json | Machine-readable output for scripting. |
- uses: buildchute/upload@v1
with:
file: build/Acme.ipa
channel: staging
token: ${{ secrets.BUILDCHUTE_TOKEN }}lane :beta do gym(scheme: "Acme") buildchute(channel: "staging") end
Xcode Cloud runs custom scripts from a ci_scripts directory beside your project, and exposes the signed app path as an environment variable.
# ci_scripts/ci_post_xcodebuild.sh npx buildchute upload "$CI_AD_HOC_SIGNED_APP_PATH" --channel staging
./gradlew assembleRelease npx buildchute upload app/build/outputs/apk/release/app-release.apk
Bitrise ships as a Step you add after Xcode Archive or Gradle Runner. CircleCI ships as an orb.
orbs:
buildchute: buildchute/cli@1
jobs:
build:
steps:
- buildchute/upload:
file: app-release.apkA channel is a named stream of builds — typically staging, beta and release. Each one has a stable link that always points at its newest ready build:
https://buildchute.com/acme/staging/latest
Give QA the channel link once and stop re-sending build links. Individual builds keep their own permanent links for when you need to pin someone to an exact version.
Every link carries one of four access modes.
| Mode | Who can install |
|---|---|
| public | Anyone with the link, until it expires. |
| password | Anyone with the link and the password. Nothing is shown before unlock. |
| allowlist | Only the email addresses you name. Each gets a personal link. |
| revoked | Nobody. Existing links stop working immediately. |
Links are always time-limited, and the binary itself is fetched over a signed URL that expires separately. A forwarded link stops working on its own rather than living forever in someone's chat history.
Builds are deleted automatically when their retention window ends — 7 days on Free through to a year on Agency. The build record and its install history stay; the binary is what gets purged.
This is deliberate. Old test builds are a liability rather than an asset: they are unreleased code, and after a few weeks nobody should be installing them anyway.
iOS is where installs actually fail, so it is worth understanding what the platform requires.
An ad-hoc build installs only on devices whose UDID is listed in the profile it was signed with. Buildchute reads that list on upload, so a tester whose device is missing is told before they tap — with their device ID ready to send you — instead of watching iOS fail with no explanation.
iOS installs come from an itms-services:// link, which only Safari handles. Chrome or an in-app browser on an iPhone cannot complete the install, so the install page detects it and says so rather than failing silently.
Signing certificates expire, and once one does, iOS refuses the build even though it downloads fine. We read the expiry date on upload and warn you ahead of time instead of letting you discover it mid-release.
On iOS 16 and later, development-signed builds need Developer Mode enabled in Settings → Privacy & Security. Ad-hoc builds do not.
An Android App Bundle is a publishing format for Play — it is not installable on a device. Upload the APK that assembleRelease produces. If you only have an .aab, convert it with bundletool first.
.aab is rejected with an explanation rather than accepted and left to fail on the tester's phone.Android asks the user to allow installs from the browser they downloaded through. That prompt is expected for any app not installed from the Play Store, and the install page warns testers so it doesn't read as a security problem.
Point a URL at your workspace and Buildchute posts JSON when things happen. Every delivery is signed with an HMAC-SHA256 header so you can verify it came from us.
| Event | Fires when |
|---|---|
| build.uploaded | A binary finished uploading, before parsing. |
| build.ready | Parsing finished and the build is installable. |
| build.failed | The binary could not be parsed — wrong format, corrupt archive. |
| build.installed | A tester completed an install. |
| build.expired | A build's retention window ended and the binary was purged. |
| cert.expiring | A signing certificate is within 14 days of expiry. |
Failed deliveries retry with backoff. Store the delivery ID and ignore repeats — retries mean you will occasionally see the same event twice.
There are two credential types, and the difference matters.
| Type | Scope |
|---|---|
| Upload token | One app. Can upload builds and nothing else. Safe in CI secrets. |
| Workspace key | Full API access across apps. Server-side only — never in CI logs or a client. |
Use an upload token in CI. If it leaks, the worst case is someone uploading a build to one app — not reading your back catalogue or deleting it.
Almost always an unregistered device. Open the install page's Devices tab to see the profile's device list and whether the current one is in it.
They are not in Safari. iOS silently ignores itms-services:// in other browsers.
Usually an expired certificate, or a development-signed build on iOS 16+ without Developer Mode enabled.
An .aab uploaded as if it were installable, or an APK for a different ABI than the device. Check the build's platform row.
Uploads resume, so retry the same command — it continues rather than starting over. If it still fails, check the build size against your plan's per-build limit.