Appearance
Releases, artifacts, and channels
A release is identified by an app, a version, and a build number. Each upload adds one artifact to that release. Promotion makes the release available on a channel; upload alone leaves it as a draft. Use the app detail API to find the app and channel IDs before calling these endpoints.
All paths on this page begin with /api/v1/orgs/:orgId/apps/:appId/releases. A session with the stated role works on every route. Only routes marked with a token scope accept Authorization: Bearer rd_...; a token must belong to this organization and, if app-scoped, to this app.
| Method and suffix | Minimum session role or token scope | Success | Result |
|---|---|---|---|
POST /upload | Developer or upload | 201 | { "release": ..., "artifact": ... } |
GET / | Viewer session | 200 | { "items": [release, ...] } |
GET /:releaseId | Viewer session | 200 | Release with artifacts and channels |
GET /:releaseId/artifacts/:artifactId/download-link | Viewer session | 200 | Signed URL and expiry |
DELETE /:releaseId/artifacts/:artifactId | Admin session | 204 | Remove an artifact |
PATCH /:releaseId | Developer or upload | 200 | Updated release |
POST /:releaseId/promote | Developer or promote | 200 | Updated release |
DELETE /:releaseId/channels/:channelId | Developer or promote | 200 | Updated release |
POST /:releaseId/channels/:channelId/pause | Developer or promote | 200 | Updated release |
POST /:releaseId/channels/:channelId/resume | Developer or promote | 200 | Updated release |
POST /channels/:channelId/rollback | Developer or promote | 200 | Release restored to the channel |
POST /:releaseId/revoke | Developer or promote | 200 | Revoked release |
The admin token scope satisfies a token scope check on routes that accept tokens. It does not make the session-only list, detail, or download-link routes available to a token.
Resumable artifact uploads
The server also implements tus 1.0.0 core, creation, termination, and expiration at /api/v1/orgs/:orgId/apps/:appId/releases/tus. The routes below use a developer session or an upload token, except capability discovery (OPTIONS). Send Tus-Resumable: 1.0.0 on non-OPTIONS requests. Upload sessions expire after 24 hours and are bound to the creating user, organization, and app.
| Method and suffix | Required input | Success |
|---|---|---|
OPTIONS /tus or /tus/* | None | 204 with Tus-Version, Tus-Extension, Tus-Max-Size |
POST /tus | Upload-Length and Upload-Metadata | 201 with Location and Upload-Expires |
GET /tus/:uploadId | None | 200 with Upload-Offset, Upload-Length, Upload-Expires |
PATCH /tus/:uploadId | Upload-Offset, raw bytes, Content-Type: application/offset+octet-stream | 204 with new offset |
DELETE /tus/:uploadId | None | 204 after terminating upload |
Upload-Metadata must provide Base64-encoded version, buildNumber, platform, arch, and format values (plus an optional variant) as comma-separated key value pairs. Upload-Length must be a positive integer within the configured maximum upload size. Resume at the offset returned by GET; a mismatched offset returns 409 upload_conflict. A different tus version returns 412 unsupported_tus_version. Invalid metadata encoding returns 400; missing or invalid fields return 422. An invalid chunk media type returns 415. The final PATCH creates or updates the release and returns X-Release-Id and X-Artifact-Id. It has no JSON body. Uploading an artifact with the same platform, architecture, format, and variant to an existing release can still fail with 409 artifact_conflict at completion.
For a one-request raw upload, use POST /upload below.
Upload an artifact
Upload the raw file bytes in the body and describe them in the query string. The endpoint does not accept multipart form data. The required query fields are:
| Field | Accepted values |
|---|---|
version | Nonempty string, at most 100 characters |
buildNumber | Nonempty string, at most 100 characters |
platform | android, ios, macos, windows, linux |
arch | arm64, x64, universal, armv7, x86 |
format | apk, aab, ipa, msix, dmg, pkg, zip, msi, exe, appimage, deb, rpm |
variant | Optional. Key of a declared variant. Omit it to use the platform's default |
For example:
sh
curl -X POST --data-binary @field-notes.apk \
-H "Authorization: Bearer $RD_TOKEN" \
-H "Content-Type: application/octet-stream" \
"$RD_URL/api/v1/orgs/$ORG_ID/apps/$APP_ID/releases/upload?version=1.4.0&buildNumber=104&platform=android&arch=arm64&format=apk"The 201 response contains release and artifact. Save release.id for notes and promotion. The release includes version, buildNumber, notesMd, publishedAt, revokedAt, and timestamps; publishedAt remains null until its first promotion. The artifact includes id, platform, arch, format, variant, sizeBytes, sha256, sha512, signature, and metadataJson. metadataJson is a JSON string when extraction succeeds, or null; an extraction failure does not necessarily fail the upload.
Uploading another file with the same app, version, and build number attaches it to the existing release. The combination of platform, architecture, format, and variant must be unique within that release. An exact duplicate returns 409 artifact_conflict. APK uploads with a signing certificate different from the one recorded for the app return 409 signer_mismatch. Files exceeding the configured upload limit return 413 upload_too_large. The server also checks the file type and rejects an empty body.
Build variants
Some apps ship several builds of the same platform and architecture: an Android TV APK next to the phone APK, or Windows builds for OpenGL and Direct3D 11. Declare each one per app, then upload with the variant field.
- The built-in
defaultvariant always exists and is shown as "Default". Existing artifacts and uploads without a variant use it. - Declare variants in the dashboard (app page, Variants) or with
/api/v1/orgs/:orgId/apps/:appId/variants. A key is 1 to 32 lowercase letters, digits or dashes and cannot change. A variant may be limited to one platform. - A declared variant can be the default for its platform. Uploads without
variantthen go to it, feeds serve it without?variant=, and the public page gives it the primary button. Only one variant per platform is the default. - An unknown key, or one limited to another platform, returns
422 unknown_variantbefore the body is read. Deleting a variant that has artifacts returns409 variant_in_use. - With
rdesk upload --variant tvor the action'svariantinput, the CLI also warns when the architecture detected in the file differs from--arch.
List and inspect releases
GET / returns all releases for the app, newest created first, in an items array. There is no pagination parameter on this route. Each release includes artifacts and channels. GET /:releaseId returns one such release. A channels entry includes releaseId, channelId, channelName, rolloutPercent, paused, and promotedAt. An unpromoted release has an empty channels array.
These reads require a dashboard session with at least viewer access. A token with read scope can fetch app details, but these release reads currently remain session-only. An unknown release returns 404 release_not_found.
Edit release notes
http
PATCH /api/v1/orgs/org_123/apps/app_456/releases/rel_789
Content-Type: application/json
Authorization: Bearer rd_your_upload_token
{"notesMd":"## What's new\n\n- Faster launch on Android"}notesMd is Markdown. Send null or an empty or whitespace-only string to clear the notes. The server stores notes on the release, so the same notes apply to all of its artifacts and channels. The response is the updated release with artifacts and channels. If you display the notes yourself, treat them as untrusted text; the server sanitizes its own rendered output.
Promote and manage rollouts
Promotion requires a channel ID from app details. The percentage is an integer from 1 to 100 and defaults to 100:
http
POST /api/v1/orgs/org_123/apps/app_456/releases/rel_789/promote
Content-Type: application/json
Authorization: Bearer rd_your_promote_token
{"channelId":"chn_stable","rolloutPercent":25}The response is the release with its updated channels array. The first promotion sets publishedAt. Promoting the same release to the same channel again updates its rollout percentage and promotion time. A revoked release cannot be promoted (409 release_revoked). Promotion can also trigger configured automatic tester distribution and a webhook; those integrations run separately from the API response.
For update feeds, a rollout below 100% uses a stable bucket derived from X-Device-Id. Clients without a device ID can receive only 100% rollouts. The public download page does not make a per-device rollout choice, so use a feed request to check what a particular client sees.
Other channel actions use the same release and channel IDs:
http
POST /api/v1/orgs/org_123/apps/app_456/releases/rel_789/channels/chn_stable/pause
POST /api/v1/orgs/org_123/apps/app_456/releases/rel_789/channels/chn_stable/resume
DELETE /api/v1/orgs/org_123/apps/app_456/releases/rel_789/channels/chn_stablePause and resume change the promotion's paused flag. Demotion removes the release from that channel but leaves the release and artifacts intact. All three return the updated release. A missing channel returns 404; pause and resume also return 404 release_not_promoted when the release has no promotion on that channel. Demoting an already absent promotion has no further effect. To change the percentage, call promote again with the new rolloutPercent.
Rollback operates on a channel rather than a named release:
http
POST /api/v1/orgs/org_123/apps/app_456/releases/channels/chn_stable/rollback
Authorization: Bearer rd_your_promote_tokenIt demotes the channel's most recent promotion and promotes the previous release at 100%. It returns that restored release. If there is no previous release, the response is 409 no_previous_release. Check the returned release ID and feed behavior after rollback, especially if the earlier release lacks an artifact for a platform you ship.
Revoke a release
POST /:releaseId/revoke marks a release revoked. The promotion records remain in the release response, but feeds exclude the revoked release and its artifacts return 404 from download URLs. The response contains the release and its revokedAt time. This is a release-wide action: it affects every artifact and channel, so use demotion if you only intend to remove one channel's promotion.
Signed download links
A viewer with a dashboard session may call GET /:releaseId/artifacts/:artifactId/download-link. The artifact must belong to that release. The response is { "url": "https://.../dl/art_...?exp=...&sig=...", "expiresAt": "..." }. The URL is a temporary credential for a private artifact; share it only with its intended recipient. Follow redirects when downloading, since storage may be served directly or through a redirect. For public-channel artifacts, the ordinary /dl/:artifactId URL can be used without this link.
For response envelopes, validation failures, and request IDs, see API conventions. For a complete first upload, see Publish your first release.