CLI reference

@betadrop/cli publishes an .ipa or .apk and prints the install link. It is the same client the GitHub Action runs, so a workflow behaves exactly like your laptop. This page is the reference; the CLI overview is the argument for using one.

Install

Global install
npm install -g @betadrop/cli   # requires Node 18 or newer

bd --version                   # or: betadrop --version

The package installs two binaries that are the same program: bd for typing and betadrop for scripts, where a two-letter command in someone else's pipeline is a collision waiting to happen. Examples on this page use betadrop for that reason. Node 18 is the floor, which every GitHub-hosted runner image already exceeds — no setup step is needed there.

Authenticate

There are two credentials paths and the choice is really "can a browser open here". On a workstation, run betadrop login and pick a method: Google or email both open your browser to approve the device, and the third option takes an API token pasted in. On a runner, skip login entirely and set BETADROP_TOKEN.

The three ways to be logged in
betadrop login                              # interactive: Google, email, or paste a token
betadrop login --token bd_live_xxxxxxxx     # non-interactive, same result
BETADROP_TOKEN=bd_live_xxxxxxxx betadrop …  # CI: no stored credential at all

BETADROP_TOKEN wins over anything stored on disk, and when it is set the CLI neither reads nor writes the credentials file — which is what makes it safe on a shared runner. It is also why betadrop login tells you it is set and declines to do anything, rather than silently storing a second credential that will never be used.

Otherwise credentials are written to $XDG_CONFIG_HOME/betadrop/config.json when that variable is set, and ~/.betadrop/config.json otherwise — including on Windows. The file is created with owner-only permissions (0600).

betadrop logout revokes the token on the server and then deletes the local file. The order matters: it clears local credentials even if the revoke call fails, so an offline logout still leaves nothing behind on the machine. Create and revoke tokens in the dashboard under Settings → Developer → API tokens.

Commands

Commands accepted by the BetaDrop CLI
CommandWhat it does
betadrop loginAuthenticate. --token <token> logs in non-interactively. Already signed in, it offers to keep the session, switch accounts, or log out first.
betadrop logoutRevoke the stored token server-side and delete the local credentials file.
betadrop whoamiPrint the authenticated account, its role, and the active token's name, expiry and last-used date. --json prints the same as an object.
betadrop publish <file>Upload a build and print its install link. Flags: --name, --notes, --ci, --json — see below.
betadrop --versionPrint the CLI version. --help lists commands; add it after a command for that command's flags.

publish

The command everything else exists to support. It takes exactly one file, which must end in .ipa or .apk.

Flags accepted by betadrop publish
FlagEffect
--name <name>Override the build name shown on the install page. Without it, the name is read out of the archive.
--notes <notes>Release notes for this build, shown to the tester on the install page. A commit message is the obvious thing to pass from CI.
--ciNon-interactive: no progress bar, no QR block, no summary. The install URL is the last line of standard output, so capture it with url="$(betadrop publish app.apk --ci | tail -n 1)" — which is exactly what our own GitHub Action does.
--jsonPrint a JSON object instead of the human summary, and suppress the progress bar the same way --ci does.

Output shapes

Three modes, and picking the wrong one is the usual reason a pipeline ends up parsing a progress bar. Default is for humans: a progress bar while uploading, then the app name and version, the install link, and a QR code drawn in the terminal. --ci reduces that to the URL alone. --json gives you the identifiers as well:

betadrop publish --json
$ betadrop publish ./MyApp.ipa --json
{
  "id": "…",
  "short_id": "…",
  "install_url": "https://betadrop.app/install/?i=…"
}

The printed URL uses the ?i= query form. The shorter /install/<id> path form resolves to the same page and neither is deprecated — links already shared in the query form keep working indefinitely, which is the reason both exist.

Two checks before any bytes leave

  • Extension. Anything other than .ipa or .apk is rejected outright.
  • Magic bytes. IPAs and APKs are both ZIP archives, so the first four bytes must be PK\x03\x04. This catches the classic mistake — a file renamed to .ipa — locally, instead of after uploading several hundred megabytes for the server to reject.

Paths with spaces

Quote them. Xcode output paths routinely contain spaces, and an unquoted path arrives as several arguments. If rejoining those fragments happens to point at a real file the CLI uses it and warns you to quote it next time; if it does not, you get a quoting hint rather than a baffling "unsupported file type" error.

Environment variables

Environment variables read by the BetaDrop CLI
VariableEffect
BETADROP_TOKENThe API token to authenticate with. Takes precedence over stored credentials, and suppresses all reads and writes of the credentials file. This is the CI mechanism.
BETADROP_API_URLOverride the API host. Defaults to https://api.betadrop.app. You will not need this unless you have been told you do.
BETADROP_APP_URLOverride the host used to build the printed install link. Defaults to https://betadrop.app.
XDG_CONFIG_HOMEStandard XDG variable. When set, credentials live at $XDG_CONFIG_HOME/betadrop/config.json instead of ~/.betadrop/config.json.

Exit codes

Exit codes returned by the BetaDrop CLI
CodeMeaning
0Success.
1Runtime or authentication failure — an expired or revoked token, a network error, a rejected upload.
2Usage error: a missing file, an unsupported extension, a file that is not really a ZIP archive, or too many arguments from an unquoted path.
130Cancelled at an interactive prompt (Ctrl-C).

The distinction between 1 and 2 is worth wiring into a pipeline: a 2 will fail again on retry because the input is wrong, while a 1 may well be transient.

Using it in CI

Generic CI runner
# Any runner with Node on it. No interactive login: the token is the credential.
npm install -g @betadrop/cli

export BETADROP_TOKEN="$BETADROP_TOKEN"   # from your CI secret store
url="$(betadrop publish ./build/app-release.apk --ci)"

echo "Install: $url"

That is the whole pattern, and it is what the GitHub Action does for you — it installs this package and shells out to this command, which is why its behaviour is documented here rather than duplicated there. On GitLab CI, Bitrise, CircleCI or Jenkins, the snippet above is the integration in full.

Two habits worth adopting. Pin the version — npm install -g @betadrop/[email protected] or whichever version you validated — so a pipeline that passed today passes tomorrow. And capture the URL rather than only printing it: posting it to the pull request or the team channel is what turns a green build into something a tester can act on. For worked examples of both, see the GitHub Actions guide and the fastlane guide.

iMobile Designs
Developed by iMobile Designs
Made with
in India