REST API
Upload builds from CI and manage links programmatically. Create a key under
API keys
and send it as Authorization: Bearer od_….
Command-line uploader
The Rust CLI in cli/
wraps this API with a progress bar and prints every link you need.
cargo install --path cli # from a checkout of this repository
opendrop login --server https://opendr0p.com --key od_…
opendrop upload build/MyApp.ipa --notes "Fixes login" --expires 30
opendrop upload app-release.apk --json | jq -r .data.storage_url
OPENDROP_SERVER=https://opendr0p.com OPENDROP_API_KEY=od_… opendrop upload app.exe -q
/api/v1/builds
Upload a build
Multipart form with the package in file. Optional fields:
release_notes— text shown on the install pageexpires_in_days— 1–3650, ornever(default 7)password— protect the install linkmax_downloads— stop serving after N installs
curl -fsS https://opendr0p.com/api/v1/builds \
-H "Authorization: Bearer $OPENDROP_API_KEY" \
-F file=@build/MyApp.ipa \
-F release_notes="$(git log -1 --pretty=%s)" \
-F expires_in_days=30
Response 201. app_url
and latest_download_url
are permanent and always point at the newest build of the same bundle id;
storage_url
links straight to the object in storage (presigned for 7 days, permanent with a
public base URL, null
on local disk).
{"data": {
"id": "Ab3dE6gH9k",
"name": "MyApp",
"platform": "ios",
"version": "1.4.0",
"build_number": "212",
"install_url": "https://opendr0p.com/i/Ab3dE6gH9k",
"app_url": "https://opendr0p.com/a/myapp",
"latest_download_url": "https://opendr0p.com/a/myapp/download",
"download_url": "https://opendr0p.com/d/Ab3dE6gH9k",
"storage": "Cloudflare R2",
"storage_url": "https://builds.example.r2.cloudflarestorage.com/builds/Ab3dE6gH9k/MyApp.ipa?X-Amz-…",
"storage_url_expires_at": "2026-11-11T10:00:00Z",
"status": "live",
"expires_at": "2026-12-04T10:00:00Z"
}}
/api/v1/builds?platform=android&q=1.4&limit=50
List builds
Newest first. Filters are optional.
/api/v1/builds/:id
Get a build
/api/v1/builds/:id
Update link settings
JSON or form fields: release_notes, enabled, max_downloads, password, remove_password, expires_in_days.
curl -X PATCH https://opendr0p.com/api/v1/builds/Ab3dE6gH9k \
-H "Authorization: Bearer $OPENDROP_API_KEY" \
-H "Content-Type: application/json" \
-d '{"enabled": false}'
/api/v1/builds/:id
Delete a build
/api/v1/apps
List apps
/api/v1/me
Current user
GitHub Actions
- name: Upload to OpenDrop
run: |
URL=$(curl -fsS https://opendr0p.com/api/v1/builds \
-H "Authorization: Bearer ${{ secrets.OPENDROP_API_KEY }}" \
-F file=@app/build/outputs/apk/release/app-release.apk \
| jq -r .data.install_url)
echo "Install: $URL" >> "$GITHUB_STEP_SUMMARY"
Errors
Errors return JSON {"error": "code", "message": "…"}
with status 400 (missing file), 401 (bad key), 404, 413 (too large), 422 (invalid package)
or 502 (the storage backend rejected the upload).