Documentation

Ship a build in five minutes

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.

Overview

Buildchute has three moving parts. Everything in these docs is one of them or a rule about them.

ConceptWhat it is
AppOne mobile app. Holds a bundle ID and its builds.
BuildOne uploaded binary, with the version, size, icon and signing data we read out of it.
ChannelA 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.

Buildchute replaces the distribution step, not code signing. You keep signing in Xcode, Fastlane or Gradle exactly as you do now.

Quickstart

1. Get a token

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.

2. Upload a build

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

3. Share the link

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.

text
✓ Uploaded Acme 1.4.2 (2891) — 48.2 MB in 6s
  https://buildchute.com/b/a8f3k
  https://buildchute.com/acme/staging/latest

CLI reference

The CLI is the primary interface. It needs no install step — npx fetches it, and CI images cache it between runs.

CommandWhat it does
buildchute upload <file>Upload an .ipa or .apk and print its install link.
buildchute listList 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 whoamiShow which app and workspace the current token belongs to.

Upload flags

FlagEffect
--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.
--jsonMachine-readable output for scripting.

CI recipes

GitHub Actions

yaml
- uses: buildchute/upload@v1
  with:
    file: build/Acme.ipa
    channel: staging
    token: ${{ secrets.BUILDCHUTE_TOKEN }}

Fastlane

ruby
lane :beta do
  gym(scheme: "Acme")
  buildchute(channel: "staging")
end

Xcode Cloud

Xcode Cloud runs custom scripts from a ci_scripts directory beside your project, and exposes the signed app path as an environment variable.

bash
# ci_scripts/ci_post_xcodebuild.sh
npx buildchute upload "$CI_AD_HOC_SIGNED_APP_PATH" --channel staging

Gradle

bash
./gradlew assembleRelease
npx buildchute upload app/build/outputs/apk/release/app-release.apk

Bitrise and CircleCI

Bitrise ships as a Step you add after Xcode Archive or Gradle Runner. CircleCI ships as an orb.

yaml
orbs:
  buildchute: buildchute/cli@1
jobs:
  build:
    steps:
      - buildchute/upload:
          file: app-release.apk

Channels

A 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:

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

Access control

Every link carries one of four access modes.

ModeWho can install
publicAnyone with the link, until it expires.
passwordAnyone with the link and the password. Nothing is shown before unlock.
allowlistOnly the email addresses you name. Each gets a personal link.
revokedNobody. 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.

Testers never create accounts. Access is a property of the link, not of a user record.

Retention

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

iOS is where installs actually fail, so it is worth understanding what the platform requires.

Devices must be in the provisioning profile

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.

Adding a device is not something we can do for you. It requires registering the UDID with Apple, regenerating the profile, and uploading a new build.

Safari only

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.

Certificate expiry

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.

Developer Mode

On iOS 16 and later, development-signed builds need Developer Mode enabled in Settings → Privacy & Security. Ad-hoc builds do not.

Android

Upload an APK, not an AAB

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.

Uploading an .aab is rejected with an explanation rather than accepted and left to fail on the tester's phone.

Unknown sources

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.

Webhooks

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.

EventFires when
build.uploadedA binary finished uploading, before parsing.
build.readyParsing finished and the build is installable.
build.failedThe binary could not be parsed — wrong format, corrupt archive.
build.installedA tester completed an install.
build.expiredA build's retention window ended and the binary was purged.
cert.expiringA 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.

API keys

There are two credential types, and the difference matters.

TypeScope
Upload tokenOne app. Can upload builds and nothing else. Safe in CI secrets.
Workspace keyFull 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.

Troubleshooting

"Unable to install" with no other detail on iOS

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.

Nothing happens when the tester taps Install

They are not in Safari. iOS silently ignores itms-services:// in other browsers.

The app installs but immediately closes

Usually an expired certificate, or a development-signed build on iOS 16+ without Developer Mode enabled.

Android says the package appears invalid

An .aab uploaded as if it were installable, or an APK for a different ABI than the device. Check the build's platform row.

The upload keeps failing partway

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.