Revyl Documentation

Builds

CI builds

Get your app binary into Revyl from any CI system

Patterns for getting your app binary into Revyl from any CI system. To refresh the main or staging Atlas after merges, use the maintenance recipe.

Set up with an agent

Paste one prompt and your coding agent does the rest.

Setup prompt

Upload this app's builds to Revyl from CI.

Read https://docs.revyl.com/builds/ci-builds.md and follow it. It's the current Revyl guide for this, so prefer it over anything you remember about Revyl.

Add a `revyl build upload --file` step, with the app's ID in `--app`, right after the CI step that already builds the iOS simulator `.app` or the Android debug `.apk`, so each pull request's build lands on its branch. Keep `REVYL_API_KEY` in the CI secret store; I'll add it myself.

You're done when a CI run uploads a build that `revyl build list` shows for the app.

Ask me before anything that needs my login or approval.

Choose where CI uploads go

Without --branch, an upload goes to the job's Git branch, created on the first upload. GitLab CI and Jenkins check out a commit, so their uploads go to the app's default branch; to follow a merge request, add --branch "$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME" ($CI_COMMIT_REF_NAME on branch pipelines).

GoalUpload with
Keep each pull request's builds apartNo --branch (the default).
Keep all pull request builds on one branch--branch pr-builds. Its Latest build is whichever pull request uploaded last.

Don't send pull request builds to main or the default branch: every run that uses it would run the newest pull request build. Merge queue runs build branches named gh-readonly-queue/..., one per entry, and bot branches (Dependabot, Renovate) each become a Revyl branch too; to keep them out of your branch lists, pass --branch in those workflows. To test the build you just uploaded, pass its build ID to the run, whatever branch it landed on. Uploads don't pin the branch. See Build branches.

Pattern 1: Upload from a URL (simplest)

If your build system produces a downloadable artifact URL (EAS, Bitrise, AppCenter, S3, GCS, GitHub Actions artifacts) then you can skip the local download entirely. The Revyl backend downloads the artifact from the URL, validates, and stores the artifact server-side using this command:

revyl build upload --url https://your-ci.com/artifacts/app-latest.apk --app "My Android App"

For authenticated URLs:

revyl build upload \
  --url https://artifacts.internal.company.com/builds/app.ipa \
  --header "Authorization: Bearer $ARTIFACT_TOKEN" \
  --app "My iOS App"

Pattern 2: Revyl CLI in GitHub Actions

Use the CLI after your workflow builds the app. The CLI detects GitHub Actions PR metadata automatically, and the upload lands on the pull request's source branch. Using the PR head SHA as the version makes the label recognizable; matching uses SCM metadata, not the version label.

- name: Install Revyl CLI
  run: |
    curl -fsSL https://revyl.com/install.sh | sh
    echo "$HOME/.revyl/bin" >> "$GITHUB_PATH"

- name: Upload Build
  id: upload
  run: |
    revyl build upload \
      --file ./build/app.apk \
      --app "$REVYL_ANDROID_APP_ID" \
      --platform android \
      --version "$REVYL_PR_HEAD_SHA" \
      --yes \
      --json > build.json
    BUILD_ID=$(jq -er 'select(.success == true and .count == 1) | .build.build_id | select(type == "string" and length > 0)' build.json)
    echo "build_id=$BUILD_ID" >> "$GITHUB_OUTPUT"
  env:
    REVYL_API_KEY: ${{ secrets.REVYL_API_KEY }}
    REVYL_ANDROID_APP_ID: ${{ vars.REVYL_ANDROID_APP_ID }}
    REVYL_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha || github.sha }}

- name: Run Tests
  run: revyl workflow run smoke-tests --android-app "$REVYL_ANDROID_APP_ID" --android-build-id "$REVYL_BUILD_ID"
  env:
    REVYL_BUILD_ID: ${{ steps.upload.outputs.build_id }}
    REVYL_API_KEY: ${{ secrets.REVYL_API_KEY }}
    REVYL_ANDROID_APP_ID: ${{ vars.REVYL_ANDROID_APP_ID }}

Pattern 3: Build on CI, Upload File

Build on the runner and upload the artifact directly. Set APP_ID to your Android app ID; the workflow below targets that exact uploaded build:

curl -fsSL https://revyl.com/install.sh | sh
export PATH="$HOME/.revyl/bin:$PATH"
revyl build upload --file ./build/app.apk --app "$APP_ID" --platform android --yes --json > build.json
BUILD_ID=$(jq -er 'select(.success == true and .count == 1) | .build.build_id | select(type == "string" and length > 0)' build.json)
revyl workflow run smoke-tests --android-app "$APP_ID" --android-build-id "$BUILD_ID"

For GitHub PR or GitLab MR matching on other runners, add --repo namespace/name --commit "$BUILT_COMMIT_SHA" to the upload step. Supply the full commit SHA actually built and the review's base repository. For GitLab, preserve subgroup paths and run from a checkout whose origin points to the connected instance. A merged-result artifact does not match the source head. These flags also work with URL uploads and override detected SCM identity. See Use your own CI for the review policy.

iOS on macOS runners

jobs:
  test:
    runs-on: macos-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: |
          bun install
          bunx expo prebuild --platform ios
          cd ios && pod install
          cd ios && xcodebuild -workspace YourApp.xcworkspace -scheme YourApp -configuration Release -sdk iphonesimulator -destination 'generic/platform=iOS Simulator' -derivedDataPath build ARCHS=arm64
      - env:
          REVYL_API_KEY: ${{ secrets.REVYL_API_KEY }}
          REVYL_APP_ID: ${{ vars.REVYL_IOS_APP_ID }}
        run: |
          curl -fsSL https://revyl.com/install.sh | sh
          export PATH="$HOME/.revyl/bin:$PATH"
          revyl build upload --file ios/build/Build/Products/Release-iphonesimulator/*.app --app "$REVYL_APP_ID" --platform ios --yes --json > build.json
          BUILD_ID=$(jq -er 'select(.success == true and .count == 1) | .build.build_id | select(type == "string" and length > 0)' build.json)
          revyl workflow run smoke-tests --ios-app "$REVYL_APP_ID" --ios-build-id "$BUILD_ID"

GitHub Actions includes macOS runners on all paid plans.

Android on Linux runners

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: oven-sh/setup-bun@v2
      - run: |
          bun install
          bunx expo prebuild --platform android
          cd android && ./gradlew assembleRelease
      - env:
          REVYL_API_KEY: ${{ secrets.REVYL_API_KEY }}
          REVYL_APP_ID: ${{ vars.REVYL_ANDROID_APP_ID }}
        run: |
          curl -fsSL https://revyl.com/install.sh | sh
          export PATH="$HOME/.revyl/bin:$PATH"
          revyl build upload --file android/app/build/outputs/apk/release/*.apk --app "$REVYL_APP_ID" --platform android --yes --json > build.json
          BUILD_ID=$(jq -er 'select(.success == true and .count == 1) | .build.build_id | select(type == "string" and length > 0)' build.json)
          revyl workflow run smoke-tests --android-app "$REVYL_APP_ID" --android-build-id "$BUILD_ID"

Pattern 4: EAS Cloud + URL Upload (no Mac, no local build)

For Expo teams, delegate the build to EAS and just ingest the result:

ARTIFACT_URL=$(npx eas-cli build --profile development --platform ios --non-interactive --json | jq -r '.[0].artifacts.buildUrl')
revyl build upload --url "$ARTIFACT_URL" --app "My iOS App"

The EAS command requires Expo authentication; use EXPO_TOKEN for unattended CI. The Revyl upload separately requires REVYL_API_KEY. Zero Mac is needed on the customer side: Expo's cloud does the build, and Revyl ingests the artifact.

iOS artifacts named .gz, .tar.gz, and .tgz work with both URL and CLI file uploads. Revyl accepts gzip-compressed tar archives and ZIP-content EAS artifacts using those suffixes. Standalone simulator App Clips are also supported; upload the Clip separately when you want to test it instead of its host application.

Pattern 5: revyl build (config-driven)

If .revyl/config.yaml has build commands configured, build in the cloud with:

revyl build --profile release --platform ios --json

This runs setup_commands followed by build_commands on a Revyl cloud runner and registers the artifact. A successful waited remote result has status: success and build_version_id; pass that ID to Explore/tests. Direct upload/local-build output uses .build.build_id instead. Configure app_id and pass --profile and --platform explicitly in CI. See Build Configuration for cloud prerequisites and source handling.

revyl test run --build and revyl workflow run --build build on the runner, not in the cloud.

Pattern 6: Preview URL (no device at upload time)

--preview prints a URL that auto-starts a simulator with that exact build when a signed-in org member opens it. Upload itself does not start a device.

revyl build upload --file android/app/build/outputs/apk/release/*.apk \
  --platform android --yes --preview --json

--json stays the same envelope and adds optional preview_url on the build object (/sessions/launch?buildId=<id>). Post that URL on the PR. Opening it starts a simulator and opens the normal session viewer. The launch page reads platform from the uploaded build's app. Refreshing /sessions/launch while a start is in flight opens the same session. After that start settles, opening the durable preview URL again starts a new device. Refreshing the live session does not start another device.

For a link that follows the app's default build, use /sessions/launch?appId=<app_id>. It resolves each time it is opened: the default branch's pin, or its Latest build. Copy it from the Apps page row menu ("Copy preview link"). It doesn't follow pull request branches, so for a pull request post the upload's preview_url instead.

CI-Friendly Flags

Upload flags (revyl build upload):

FlagEffect
--jsonMachine-readable JSON output
--branch <name>Upload to this Revyl branch, created if missing, instead of the default destination
--pinPin the branch to the uploaded build
--previewEmit a preview_url that auto-starts a simulator when opened
--version <label>Version label (default: <git-branch-slug>-YYYYMMDD-HHMMSS, or the timestamp alone on a detached checkout)
--repo <namespace/name>Base GitHub or GitLab repository for review matching; requires --commit
--commit <sha>Full commit SHA actually built; requires --repo
--pr <number>Select a PR or MR explicitly; requires --repo and --commit
--yes / -yConfirm prompts automatically

Run flags (revyl test run, revyl workflow run):

FlagEffect
--jsonMachine-readable JSON output
--no-waitQueue the run and exit without waiting for results
--retries NRetry attempts for failed tests (1-5)
--branch <name>Run on this Revyl branch instead of the saved choice; it never falls back

--quiet / -q suppresses non-essential output on every command.

Next Steps

On this page