Skip to content

Build Distribution ​

Connect your CI pipeline so every build lands in App Center automatically — no manual uploads.

How it works ​

CI pipeline
  → POST /api/v1/builds (tapflow relay)
  → Build appears in App Center
  → Team opens browser, picks a device, and tests
      PO / PM: does it match the spec?
      Designer: does it match the design?
      Backend: is the API wired up correctly?
      QA: any bugs to report?

The CI job uploads the build artifact. From there, anyone on the team can test directly in the browser — no IDE, no device setup needed.

Two testing paths

This guide covers the manual review path: CI delivers the build; people do the testing.

For automated testing where an LLM agent controls the simulator, see MCP in CI/CD. That is a separate, experimental feature.

Any build tool ​

tapflow works on the built artifact, not the build tool. A native Xcode or Gradle build, React Native, Flutter, or a cloud build service all produce a build you upload the same way. Once you have an artifact (.app.zip, .tar.gz/.tgz, or .apk), the rest of this page is the same.

Prerequisites ​

RequirementNotes
tapflow relayRunning and reachable from your CI environment
Personal access token (PAT)Create an API-type token in Settings → Tokens (it carries builds:write). Only Admins see this page

How CI reaches the relay ​

Your CI job has to reach the relay's POST /api/v1/builds. The relay is meant to stay on the same internal network as the agents (Deployment options), so the path depends on where CI runs.

Relay setupHow CI uploads
LAN only (default)Cloud runners (GitHub-hosted and the like) cannot reach a LAN relay. Upload from a self-hosted runner on the internal network, using the relay's internal address (http://192.168.x.x:4000)
VPS + rathole tunnelOpen the relay for external access and CI can upload from anywhere via the public URL (https://your-vps.com) — the smoothest fit for cloud CI
Tailscale tunnelOnly tailnet members can connect, so the CI runner has to be on the tailnet

The relay does not go on a cloud host

Deploying the relay to fly.io, Railway, or similar puts the agent→relay path over the internet and the stream breaks (unsupported). When you need public access, keep the relay on the internal network and expose it through a tunnel. The VPS is the tunnel host, not the relay host.

1. Generate a token ​

In the dashboard, go to Settings → Tokens → New token. Only Admins see the Tokens page, so use an Admin account.

  • Name: something descriptive, e.g. GitHub Actions
  • Expiration: 30 days by default. Pick 7, 30, 60 or 90 days, a custom number (1–365), or No expiration. For CI, 90 days or less is recommended.
  • Type: API, which grants view, builds:write

Copy the token — it is shown only once. Store it as a CI secret (e.g. TAPFLOW_PAT).

2. Upload the build ​

Call POST /api/v1/builds after the build artifact is ready.

sh
# iOS (.app.zip)
curl -X POST https://your-relay/api/v1/builds \
  -H "Authorization: Bearer $TAPFLOW_PAT" \
  -F "file=@MyApp.app.zip" \
  -F "status=In Progress" \
  -F "label=$GIT_BRANCH"

# Android (.apk)
curl -X POST https://your-relay/api/v1/builds \
  -H "Authorization: Bearer $TAPFLOW_PAT" \
  -F "file=@MyApp.apk" \
  -F "status=In Progress" \
  -F "label=$GIT_BRANCH"

status=In Progress signals to the team that the build is ready for review.
Use label to attach context — branch name, ticket number, or a short description.

iOS builds

.ipa files are not supported. For a simulator build, upload .app.zip or .tar.gz/.tgz. Build .app.zip with xcodebuild -sdk iphonesimulator and zip the .app folder; a .tar.gz/.tgz is what a cloud simulator build produces.

3. Post build metadata (optional) ​

Attach commit and branch info as a comment so reviewers know what changed.

sh
BUILD_ID=$(curl -sf -X POST https://your-relay/api/v1/builds \
  -H "Authorization: Bearer $TAPFLOW_PAT" \
  -F "file=@MyApp.app.zip" \
  -F "status=In Progress" \
  -F "label=$GIT_BRANCH" | jq -r '.id')

curl -X POST https://your-relay/api/v1/comments \
  -H "Authorization: Bearer $TAPFLOW_PAT" \
  -F "build_id=$BUILD_ID" \
  -F "body=Branch: $GIT_BRANCH
Commit: $GIT_SHA
$GIT_COMMIT_MSG"

GitHub Actions example ​

This example assumes a self-hosted macOS runner that can reach the relay's internal address. If you have opened the relay on a public URL, as with VPS + rathole, you can switch runs-on to a cloud runner such as macos-latest.

Self-hosted runners and pull requests

A self-hosted runner executes whatever code the workflow checks out, on a Mac inside your network. The if: below skips pull requests opened from forks, so only branches in your own repository build there. Keep it, and don't attach self-hosted runners to a public repository that accepts outside pull requests.

yaml
name: Upload to tapflow

on:
  push:
    branches: [main, 'release/**']
  pull_request:

jobs:
  upload:
    if: github.event_name == 'push' || github.event.pull_request.head.repo.full_name == github.repository
    runs-on: [self-hosted, macos]

    steps:
      - uses: actions/checkout@v4

      - name: Build app
        run: |
          xcodebuild -scheme MyApp -sdk iphonesimulator \
            -configuration Debug \
            CONFIGURATION_BUILD_DIR=build/Debug
          cd build/Debug && zip -r MyApp.app.zip MyApp.app

      - name: Upload to tapflow
        env:
          TAPFLOW_PAT: ${{ secrets.TAPFLOW_PAT }}
          TAPFLOW_RELAY_URL: ${{ secrets.TAPFLOW_RELAY_URL }}
          BRANCH: ${{ github.head_ref || github.ref_name }}
          COMMIT: ${{ github.sha }}
          COMMIT_MSG: ${{ github.event.head_commit.message || github.event.pull_request.title }}
        run: |
          BUILD_RESPONSE=$(curl -sf -X POST "$TAPFLOW_RELAY_URL/api/v1/builds" \
            -H "Authorization: Bearer $TAPFLOW_PAT" \
            -F "file=@build/Debug/MyApp.app.zip" \
            -F "status=In Progress" \
            -F "label=$BRANCH")

          BUILD_ID=$(echo "$BUILD_RESPONSE" | jq -r '.id')

          COMMENT="Branch: $BRANCH"$'\n'"Commit: $COMMIT"$'\n'"$COMMIT_MSG"

          curl -sf -X POST "$TAPFLOW_RELAY_URL/api/v1/comments" \
            -H "Authorization: Bearer $TAPFLOW_PAT" \
            -F "build_id=$BUILD_ID" \
            -F "body=$COMMENT"

Build status reference ​

StatusMeaning
BacklogUploaded but not yet ready for review
In ProgressReady — team can start testing
DoneStakeholders approved
RejectedIssues found, needs fixes

CI sets In Progress on upload. Done and Rejected are set manually after review.

Released under the MIT License.