CLI reference
Last updated 16 August 2026
The appdroppernpm package uploads builds from a terminal, or from any CI that can run a shell command. It’s a thin client over the REST API — everything it does, you could do with curl.
Installing
You don’t have to. npx fetches and runs it in one step, which is what every CI example uses:
npx appdropper upload build.apkFor repeated local use, install it properly:
npm install -g appdropper # globally
npm install -D appdropper # or as a dev dependencyRequires Node 18 or newer.
Authentication
A token covers whichever apps you ticked when it was created, so one token — and one appdropper login— can serve every app you push from a machine. Uploading a build whose app isn’t covered fails with a link to add it.
The CLI looks for a token in three places, most explicit first:
- The
--tokenflag - The
APPDROPPER_TOKENenvironment variable — how CI authenticates - A saved login from
appdropper login
Prefer the environment variable in CI
A token passed as --tokencan end up in a process list or a log line. One set from your provider’s secret store is masked.
Commands
appdropper upload <file>
Uploads an .apk or .ipa, waits for it to be processed, and prints the install link.
appdropper upload build/app-release.apk
appdropper upload MyApp.ipa --notes "$(git log -1 --pretty=%B)"
appdropper upload build.apk --tag nightly --json| Flag | Default | What it does |
|---|---|---|
--token <value> | $APPDROPPER_TOKEN | API token to authenticate with |
--notes <text> | empty | Release notes shown to testers on the install page |
--tag <name> | beta | Label for the build. --group is accepted as an alias |
--timeout <seconds> | 600 | How long to wait for processing before giving up |
--json | off | Print the full result as JSON instead of a summary |
--no-qr | off | Skip the terminal QR code |
On success the install URL — and nothing else — goes to stdout, so URL=$(appdropper upload build.apk)does what you’d expect. Progress bars and status messages go to stderr, so they never contaminate a pipe.
appdropper login
Authorizes this machine. Prints a short code, opens your browser, and waits while you approve it — no password is ever typed into the terminal. On approval the token is written to ~/.appdropper/config with chmod 600.
appdropper login
appdropper login --no-browser # print the URL instead of opening itThis is for humans. CI has no browser, so pipelines always use a pre-generated token in APPDROPPER_TOKEN instead.
appdropper logout
Removes the saved token from this machine. The token itself stays valid — revoke it in Settings → API tokensif that’s what you meant.
appdropper whoami
Shows which apps the token covers, where it came from, and when it expires. The first thing to run when a pipeline is failing and nobody is sure which credential it’s using — or why an upload was rejected.
$ appdropper whoami
Token GitHub Actions adp_a1b2c3d4e5f6g7h8i9j0_••••••wxyz
Scopes upload:builds
Source APPDROPPER_TOKEN
Apps 2 apps
Acme Mobile com.acme.mobile
Acme Driver com.acme.driver
Expires 14 Nov 2026appdropper builds list
Recent builds, newest first. If the token covers more than one app, name the one you want with --app — appdropper whoami lists the IDs.
appdropper builds list
appdropper builds list --app p7Rt9... --limit 50 --jsonappdropper token rotate
Replaces the current token with a fresh one carrying the same name, apps and validity window. The old value stops working immediately. If you rotated a saved login, the config file is updated in place; otherwise the new token is printed on stdout so you can pipe it somewhere:
# Rotate the CI token and push the new value straight into GitHub Secrets
gh secret set APPDROPPER_TOKEN --body "$(appdropper token rotate --token "$OLD_TOKEN")"Environment variables
| Variable | Purpose |
|---|---|
APPDROPPER_TOKEN | The token to use — the standard way CI authenticates |
APPDROPPER_API_URL | API base URL. Defaults to https://appdropper.io/api/v1 |
APPDROPPER_CONFIG_DIR | Where the saved login lives. Defaults to ~/.appdropper |
NO_COLOR | Disable coloured output |
Exit codes
These are a contract: a pipeline can tell a retryable problem apart from a permanent one.
| Code | Meaning | Retry? |
|---|---|---|
0 | Success | — |
1 | The upload or request failed | Usually worth one retry |
2 | Bad arguments — wrong path, wrong file type | No |
3 | Missing, expired, revoked or out-of-scope token | No — fix the credential |
4 | Rate limited | Yes, after the stated wait |
JSON output
--json prints the raw API result, which is what you want if anything downstream needs more than the link:
{
"upload_id": "8fK2mQ…",
"status": "ready",
"build_id": "aBc123XyZ",
"app_id": "p7Rt9…",
"app_name": "Acme Mobile",
"version": "2.4.1",
"build_number": "318",
"bundle_id": "com.acme.mobile",
"platform": "android",
"install_url": "https://appdropper.io/acme-mobile?build=aBc123XyZ",
"qr_url": "https://appdropper.io/api/v1/qr/xY9k2.png?build=aBc123XyZ",
"expires_at": 1786224000000
}How an upload works
Three requests, which the CLI hides behind one command:
Reserve
The CLI tells the API the file name and size. Every plan limit, storage quota and rate limit is applied here, before a single byte moves, and the API returns a resumable upload URL.
Transfer
The binary goes directly from your machine to Google Cloud Storage — never through our API, which is why build size is capped only by your plan. A dropped connection resumes from where it stopped rather than starting over.
Wait
The CLI holds a long-poll open while the server unpacks the binary, reads its name, icon, version and bundle ID, files it as a build, and notifies your testers. Then it prints the link.
If you’d rather do this yourself, the API reference documents all three.
Ready to try it?
Drop an .apk or .ipa and get a shareable install link in seconds.