API reference
Last updated 16 August 2026
Everything App Dropper offers for automation runs through this API. The CLI, the GitHub Action and the Fastlane plugin are clients of it and nothing more — if one of those fits, use it. This page is for building your own integration.
https://appdropper.io/api/v1Authentication
Send an API token as a bearer token on every request except the QR endpoint. Generate one under Settings → API tokens.
curl https://appdropper.io/api/v1/me \
-H "Authorization: Bearer $APPDROPPER_TOKEN"A token grants access to a specific set of apps, chosen when it is created, and carries exactly one scope: upload:builds. It cannot delete builds, manage testers, read billing, reach an app outside its list, create new apps, or mint further tokens. Nor can it widen its own scope — adding an app happens only in the dashboard, while signed in. Call GET /v1/me to see what a token covers.
Uploading a build
Binaries do not travel through this API. Cloud infrastructure caps HTTP request bodies well below the 500 MB a Pro build may reach, so an upload is three steps: reserve, transfer directly to storage, then wait for processing.
1. Reserve the upload
POST /v1/uploads. Every plan limit, storage quota and rate limit is applied here — a 201 means the bytes are welcome.
curl -X POST https://appdropper.io/api/v1/uploads \
-H "Authorization: Bearer $APPDROPPER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"file_name": "app-release.apk",
"file_size": 42317884,
"release_notes": "Fixes the crash on the results screen",
"tag": "beta"
}'{
"upload_id": "8fK2mQ…",
"upload_url": "https://storage.googleapis.com/upload/storage/v1/b/…",
"upload_method": "PUT",
"content_type": "application/vnd.android.package-archive",
"allowed_app_ids": ["p7Rt9…", "k2Lm4…"],
"expires_at": 1786224000000
}file_sizemust be the real byte length. It’s what the quota check runs against, and an object that lands larger than it was declared is rejected during processing.
2. Send the binary
PUT the file to upload_url. This is a Google Cloud Storage resumable session, so it needs no authentication header of its own — the URL is the credential, and it’s single-use.
curl -X PUT "$UPLOAD_URL" \
-H "Content-Type: application/vnd.android.package-archive" \
--data-binary @app-release.apkTo resume after a dropped connection, ask the session what it kept and continue from there — standard GCS resumable-upload behaviour:
# Query: returns 308 with a Range header, or 200/201 if already complete
curl -X PUT "$UPLOAD_URL" \
-H "Content-Range: bytes */42317884" -H "Content-Length: 0" -i
# Resume from byte N
curl -X PUT "$UPLOAD_URL" \
-H "Content-Range: bytes N-42317883/42317884" \
--data-binary @<(tail -c +$((N+1)) app-release.apk)3. Wait for processing
GET /v1/uploads/{upload_id}. Add ?wait=<seconds> (up to 180) and the server holds the connection until the build is parsed, so you normally need one request rather than a polling loop.
curl "https://appdropper.io/api/v1/uploads/$UPLOAD_ID?wait=120" \
-H "Authorization: Bearer $APPDROPPER_TOKEN"{
"upload_id": "8fK2mQ…",
"status": "ready",
"build_id": "aBc123XyZ",
"app_id": "p7Rt9…",
"app_name": "Acme Mobile",
"app_icon": "https://…",
"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
}status is pending (bytes still expected), processing, ready, or error. A response that hasn’t settled yet comes back as HTTP 202 rather than 200, so curl --fail won’t mistake an in-flight upload for a finished one. On error, an error object explains why — including upgrade_required when a plan limit is what stopped it.
Other endpoints
GET /v1/me
Token introspection: every app it covers, its scopes, and when it expires. Apps the account no longer manages are omitted, so this reflects what the token can really do. Useful as a preflight check in a pipeline. GET /v1/apps is an alias.
GET /v1/apps/{app_id}/builds
Recent builds, newest first. Accepts ?limit= (1–100, default 20). self is accepted in place of the ID when the token covers exactly one app; with several it is ambiguous and returns 400.
curl "https://appdropper.io/api/v1/apps/self/builds?limit=5" \
-H "Authorization: Bearer $APPDROPPER_TOKEN"GET /v1/builds/{build_id}
One build in full. A build outside the token’s app returns 404, the same as one that never existed.
POST /v1/tokens/rotate
Issues a replacement for the calling token with the same name, apps and validity window, and revokes the current one immediately. Reachable with nothing but the token itself, so a long-running job can renew its own credential. It can never add apps — any the account has lost access to are dropped from the replacement.
curl -X POST https://appdropper.io/api/v1/tokens/rotate \
-H "Authorization: Bearer $APPDROPPER_TOKEN"GET /v1/qr/{share_id}.png
A PNG QR code for an install link. Public and unauthenticated, because its whole job is to be embedded in a pull-request comment or a chat message — both of which fetch images anonymously. Accepts ?size= (96–1024) and ?build= to point at a specific build.
Errors
Every failure uses the same envelope:
{
"error": {
"code": "upgrade_required",
"message": "This build is 380 MB. Your plan allows up to 150 MB per build. Upgrade to Pro at https://appdropper.io/pricing"
}
}| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | Missing or malformed parameter |
| 401 | unauthorized | No token, or one that’s expired, revoked or unknown |
| 402 | upgrade_required | A plan limit stopped it — retrying will not help |
| 403 | forbidden | The token doesn’t cover that app |
| 404 | not_found | No such upload, build, app or share link |
| 429 | rate_limited | Too many requests or uploads — the message says how long to wait |
| 500 | server_error | Our fault. Safe to retry |
The distinction between 402 and 429 is the one worth handling: a rate limit clears on its own, a plan limit never does.
Rate limits
300 requests per hour per token, counted against the token rather than the IP address — CI runners share address space, and an IP-based ceiling would have one busy repository throttle everyone else on the same runner pool.
Uploads are additionally capped by your plan: 5 per hour on free, 30 on Pro, alongside the per-build size limit and total storage quota. See Free vs. Pro limits.
Where a build lands
Exactly where a dashboard upload would. App Dropper reads the app name, icon, version, build number and bundle ID out of the binary, files it as the newest build of the token’s app, generates the share link and manifest, and notifies that app’s testers by email and push.
If the binary’s bundle ID belongs to an app the token doesn’t cover, the upload fails with a link to the page where the app can be added. If no app in the account uses that bundle ID at all, it also fails — creating apps is a dashboard action, not something a pipeline does silently.
Versioning
The version lives in the path. Fields may be addedto a response at any time, so parse leniently and ignore what you don’t recognise; anything that removes or changes the meaning of an existing field would ship as /v2.
Ready to try it?
Drop an .apk or .ipa and get a shareable install link in seconds.