Skip to content
Fluxer API

Desktop downloads

The download resource serves the desktop application builds a deployment has stored. It also serves a SHA-256 checksum for each build and the release feed files beside them. Every route here is unauthenticated. A request that sends a credential anyway receives the same status and body. No route here declares a rate limit bucket, and none draws on the global allowance.

Fluxer registers the routes under /dl, and the six desktop routes under /dl/desktop. A client builds the URL against api_client from the instance endpoints object.

The prefix mounts at the root and at /v1, so /v1/dl/desktop/... resolves for the six routes that name their own segments.

Every other route on this reference takes the /v1 form, and HTTP API states that a client MUST use it.

Every route answers GET. Five registrations also bind HEAD: the two checksum routes, the two artifact routes, and the catch-all. Get latest desktop version and List desktop versions bind GET alone, and a HEAD still reaches them under the shared rule for HEAD.

A HEAD on either JSON route runs the same resolution as the GET and returns its status and headers with no body. It can therefore answer 404.

On a checksum route, a HEAD returns 200 with no body. The headers are Content-Type, Content-Disposition, Cache-Control, and the Content-Length of the checksum line.

A HEAD on an artifact route or the catch-all reads object metadata. It returns 200 with Content-Type, Content-Disposition, Accept-Ranges, Cache-Control, Content-Length, and, where storage reports them, ETag and Last-Modified. It ignores Range and answers neither 206 nor 416. It never redirects to a presigned storage URL. Fluxer resolves a country redirect before it reads the method, so a HEAD receives that 302 exactly as a GET does.

ValueNameDescription
stableStableThe channel a deployment publishes for general use
canaryCanaryThe channel that receives a build ahead of stable

The channel selects the storage prefix and the product name in a filename. A canary file is named Fluxer-Canary or Fluxer Canary, and a stable file is named Fluxer.

ValueNameDescription
win32WindowsThe Windows build target
darwinmacOSThe macOS build target
linuxLinuxThe Linux build target

The path segment with one of those values is named plat.

ValueNameDescription
x64x64The 64-bit x86 architecture
arm64ARM64The 64-bit ARM architecture

A channel, a platform, and an architecture together name one coordinate.

ValueNameDescription
setupSetupThe Windows installer executable
portablePortableThe Windows archive that stores its data beside the executable
dmgDMGThe macOS disk image
zipZIPThe macOS application archive
appimageAppImageThe Linux portable application image
debDEBThe Debian package
rpmRPMThe RPM package
tar_gzTAR.GZThe Linux compressed tarball

The registry is closed, and a format outside it returns 400 INVALID_FORM_BODY.

Each format resolves on one platform. setup and portable resolve on win32, dmg and zip on darwin, and appimage, deb, rpm, and tar_gz on linux. A format paired with any other platform returns 404.

On darwin a dmg or zip request tries the universal file before the architecture-specific one, so x64 and arm64 can resolve to the same file.

A client appends .sha256 to the format segment to read the checksum, and sends the bare format to read the file. tar_gz.sha256 is the checksum of tar_gz.

The checksum route claims any last segment matching [a-z_]+\.sha256 before the artifact route sees it.

The pattern is lowercase, so appimage.SHA256 matches neither checksum route. It reaches the artifact route, where the format schema rejects it with the same status and code.

A version info object describes one release at a coordinate and the file it has for each format.

FieldTypeDescription
versionstringThe release version in MAJOR.MINOR.PATCH form
pub_date1ISO8601 timestampThe moment the release was published
minimum_system_version?2?stringThe lowest operating system version the release supports
files3map[string, version file object]The download entry for each package format the release has

1 Copied from the coordinate’s manifest without reformatting. A coordinate with no usable manifest reports the newest storage modification time among the objects of that release instead

2 Present only where the coordinate has a manifest and that manifest names a value

3 A format that resolves no file is absent from the map

{
"version": "1.4.2",
"pub_date": "2026-08-19T11:04:00.000Z",
"minimum_system_version": "10.15.0",
"files": {
"dmg": {
"url": "https://api.example.com/dl/desktop/stable/darwin/arm64/1.4.2/dmg",
"sha256": "3b1f5c0d9e7a24486cf0b1d3a5e87209cc4d61fba0937e5528d1c4a67b0e93f2",
"checksum_url": "https://api.example.com/dl/desktop/stable/darwin/arm64/1.4.2/dmg.sha256"
}
}
}

A version file object is the download entry for one format of one release.

FieldTypeDescription
url1stringThe absolute URL of the versioned download for this format
sha2562?stringThe hash of the file as 64 lowercase hexadecimal characters, or null where no checksum was found
checksum_url1?stringThe absolute URL of the checksum file, or null where sha256 is null

1 Built from api_client in the instance endpoints object, with no /v1 segment

2 Read from the sibling .sha256 object in storage, or from the manifest entry where the manifest named the file that resolved. A value outside ^[a-f0-9]{64}$ is reported as null

A checksum response is one sha256sum line: the hash, two spaces, the resolved filename, and a trailing newline.

2f6d1a4b8c3e07f95ab61d4c2e8035971fd0ba46c7e213985cd0f74a6b1e82c3 Fluxer-1.4.2-linux-x86_64.AppImage

The response has Content-Type: text/plain; charset=utf-8, a Content-Disposition of attachment naming the resolved filename with .sha256 appended, and a Content-Length counting the encoded line. It sets no Accept-Ranges, ETag, or Last-Modified, and it ignores a Range header.

Fluxer reads the hash from the sibling .sha256 object or from the manifest entry, and treats a value that is not 64 lowercase hexadecimal characters as absent. That case returns 404.

Fluxer resolves a coordinate to one storage key before it answers. It reads manifest.json under the coordinate prefix first and takes the filename that manifest records for the requested format. Fluxer falls back to a listing when that manifest is absent, is not valid JSON, is not a manifest object, describes another coordinate, or names a file storage does not hold. The fallback lists the objects under the prefix and takes the highest version whose filename parses for the requested format.

The listing skips any name containing /, the names manifest.json, RELEASES.json, and releases.json, and any name ending in .sha256, .blockmap, or .yml.

A coordinate that resolves no file returns 404 with the plain text body Not Found.

Every artifact response has Accept-Ranges: bytes and a Content-Disposition of attachment naming the resolved file, unless the stored object has its own Content-Disposition. The filename is percent-encoded inside the header, so a name containing a space is written with %20.

ResponseCache-Control
Latest version metadata and the version listpublic, max-age=300
Latest artifact and latest checksumno-store
Versioned artifact and versioned checksum under desktop/public, max-age=31536000
A release feed filename, and anything under desktop-test/public, max-age=300
Any desktop/ artifact on a deployment that configures country redirectsprivate, no-store
A redirect to a presigned storage URLno-store

A release feed filename is manifest.json, a name ending in .yml or .yaml, a name beginning RELEASES, or a name beginning releases or assets and ending in .json.

Two deployment settings answer a download with 302.

A deployment that reports self_hosted false on the instance features object and configures a country list for GitHub redirects resolves the caller’s country on every desktop/ artifact request. When a complete and verified release descriptor names the file, a caller in a listed country receives 302 to that GitHub release asset. Every other outcome streams from storage, and every response under this setting has Cache-Control: private, no-store.

A deployment that issues presigned download URLs answers a GET with 302 to a storage URL valid for 900 seconds. That redirect has Cache-Control: no-store and Accept-Ranges: bytes. The setting is off by default.

Every route accepts test as a query parameter. The value 1 or true, matched without regard to case, resolves the object against the desktop-test/ storage prefix. Any other value is read as false.

On the six desktop routes the flag replaces the prefix outright. On Download stored object it rewrites a key beginning desktop/ and leaves any other key unchanged, and a path that already names desktop-test/ resolves there with no flag at all.

A url and a checksum_url built for a request that sent the flag repeat ?test=1, so a client following either one stays on the test prefix.

GET/v1/dl/desktop/{channel}/{plat}/{arch}/latestUnauthenticated

Returns the version info object for the newest release at the coordinate.

FieldTypeDescription
channelstringThe release channel to resolve
platstringThe platform to resolve
archstringThe architecture to resolve
FieldTypeDescription
test?stringThe test build flag, 1 or true to resolve under desktop-test/
StatusBodyCondition
200version info objectA release resolved at the coordinate
400error responseA path or query value fails its schema and the request returns INVALID_FORM_BODY
404Not FoundNo release resolved at the coordinate

A client reads files for the format it wants. A format whose file resolved no checksum reports sha256 and checksum_url as null.

The 200 has Cache-Control: public, max-age=300. The 404 has Content-Type: text/plain.

GET/v1/dl/desktop/{channel}/{plat}/{arch}/versionsUnauthenticated

Returns the releases stored at the coordinate, newest first.

FieldTypeDescription
channelstringThe release channel to resolve
platstringThe platform to resolve
archstringThe architecture to resolve
FieldTypeDescription
limit?integerThe maximum number of releases to return (1 through 100, default 25)
before?1stringThe version to page below, exclusive
after?1stringThe version to page above, exclusive
test?stringThe test build flag, 1 or true to resolve under desktop-test/

1 Compared component by component as numbers, and both bounds can be sent together to select a range

FieldTypeDescription
versionsarray[version info object]The releases on this page, ordered newest first
has_morebooleanWhether the filtered set held more releases than limit
StatusBodyCondition
200response bodyThe listing completed, possibly with no release
400error responseA path or query value fails its schema and the request returns INVALID_FORM_BODY

This route never answers 404. A coordinate that holds no object returns 200 with an empty array.

Fluxer builds the listing from storage objects and ignores the manifest, so a release the manifest names but storage does not hold is absent here. pub_date reports the newest storage modification time among that release’s objects, and minimum_system_version is never reported.

A format is listed only where a filename under the coordinate parses for it, and sha256 is reported only where the sibling .sha256 object exists and holds a valid hash.

The 200 has Cache-Control: public, max-age=300.

GET/v1/dl/desktop/{channel}/{plat}/{arch}/latest/{format}Unauthenticated

Streams the newest file at the coordinate for one package format.

FieldTypeDescription
channelstringThe release channel to resolve
platstringThe platform to resolve
archstringThe architecture to resolve
formatstringThe package format to resolve
FieldTypeDescription
test?stringThe test build flag, 1 or true to resolve under desktop-test/
FieldTypeDescription
Range?stringThe standard byte range, answered with 206 and a Content-Range header
StatusBodyCondition
200artifact bytesThe complete file was streamed
206artifact bytesThe requested byte range was streamed
302emptyThe deployment redirects this download
400error responseA path or query value fails its schema and the request returns INVALID_FORM_BODY
404Not FoundNo file resolved for the format at the coordinate
416emptyThe requested range is unsatisfiable

The stored file keeps its own name, so the resolved filename has the release version even though the request named latest. A client that follows this route on every check reads a different file once the coordinate publishes a new release.

The 200 has Content-Type1, Content-Disposition2, Content-Length, Accept-Ranges: bytes, Cache-Control: no-store, ETag3, and Last-Modified3. The 206 has the 200 headers plus Content-Range. The 302 has Location, Accept-Ranges: bytes, and Cache-Control. The 404 has Content-Type: text/plain. The 416 has Accept-Ranges: bytes, Content-Range: bytes */{size}, and Cache-Control.

1 The stored media type, and application/octet-stream where storage reports none

2 attachment naming the resolved filename, percent-encoded

3 Sent where storage reports the value

GET/v1/dl/desktop/{channel}/{plat}/{arch}/latest/{format}.sha256Unauthenticated

Returns the checksum file for the newest file at the coordinate.

FieldTypeDescription
channelstringThe release channel to resolve
platstringThe platform to resolve
archstringThe architecture to resolve
format1stringThe package format to resolve, followed by .sha256

1 The complete segment matches [a-z_]+\.sha256 for the route to claim it, and the part before the suffix names a format in the closed registry

FieldTypeDescription
test?stringThe test build flag, 1 or true to resolve under desktop-test/
StatusBodyCondition
200checksum lineThe file and a valid hash both resolved
400error responseA path or query value fails its schema and the request returns INVALID_FORM_BODY
404Not FoundNo file resolved for the format, or the resolved file has no valid hash

This hash is the value Get latest desktop version reports as sha256 for the same format. The line names the file this coordinate’s latest download streams.

The 200 has Content-Type: text/plain; charset=utf-8, Content-Disposition, Content-Length, and Cache-Control: no-store. The 404 has Content-Type: text/plain.

GET/v1/dl/desktop/{channel}/{plat}/{arch}/{version}/{format}Unauthenticated

Streams one released file by version and package format.

FieldTypeDescription
channelstringThe release channel to resolve
platstringThe platform to resolve
archstringThe architecture to resolve
version1stringThe release version to resolve
formatstringThe package format to resolve

1 Three decimal components matching ^\d+\.\d+\.\d+$, so a two-component or suffixed version returns 400 INVALID_FORM_BODY

FieldTypeDescription
test?stringThe test build flag, 1 or true to resolve under desktop-test/
FieldTypeDescription
Range?stringThe standard byte range, answered with 206 and a Content-Range header
StatusBodyCondition
200artifact bytesThe complete file was streamed
206artifact bytesThe requested byte range was streamed
302emptyThe deployment redirects this download
400error responseA path or query value fails its schema and the request returns INVALID_FORM_BODY
404Not FoundNo file resolved for the version and format
416emptyThe requested range is unsatisfiable

Fluxer reads the manifest only where the manifest names the requested version. Otherwise it tries the known filename spellings for the coordinate in turn and streams the first one storage holds, so a version published under an older naming scheme still resolves.

The 200 has Content-Type, Content-Disposition, Content-Length, Accept-Ranges: bytes, Cache-Control1, ETag2, and Last-Modified2. The 206 has the 200 headers plus Content-Range. The 302 has Location, Accept-Ranges: bytes, and Cache-Control. The 404 has Content-Type: text/plain. The 416 has Accept-Ranges: bytes, Content-Range: bytes */{size}, and Cache-Control.

1 public, max-age=31536000 for a released file, and public, max-age=300 under desktop-test/ or for a release feed filename

2 Sent where storage reports the value

GET/v1/dl/desktop/{channel}/{plat}/{arch}/{version}/{format}.sha256Unauthenticated

Returns the checksum file for one released file.

FieldTypeDescription
channelstringThe release channel to resolve
platstringThe platform to resolve
archstringThe architecture to resolve
versionstringThe release version to resolve
format1stringThe package format to resolve, followed by .sha256

1 The complete segment matches [a-z_]+\.sha256 for the route to claim it, and the part before the suffix names a format in the closed registry

FieldTypeDescription
test?stringThe test build flag, 1 or true to resolve under desktop-test/
StatusBodyCondition
200checksum lineThe file and a valid hash both resolved
400error responseA path or query value fails its schema and the request returns INVALID_FORM_BODY
404Not FoundNo file resolved for the version and format, or no valid hash was found for it

Fluxer reads the hash from the sibling .sha256 object for the resolved file. Where that object is absent and the requested version is the newest one at the coordinate, Fluxer uses the manifest hash for that format instead. Any other case returns 404.

The 200 has Content-Type: text/plain; charset=utf-8, Content-Disposition, Content-Length, and Cache-Control1. The 404 has Content-Type: text/plain.

1 public, max-age=31536000 for a released file, and public, max-age=300 under desktop-test/

GET/dl/{path}Unauthenticated

Streams one stored object addressed by its storage key. A release feed file has no format segment and no version segment, so this is the only route that reaches one.

FieldTypeDescription
path1stringThe storage key under the downloads bucket, which can contain / separators

1 Taken from the request path with the /dl prefix removed, then normalised. It begins desktop/ or desktop-test/ after normalisation, and every other key returns 404

FieldTypeDescription
test?stringThe test build flag, 1 or true to rewrite a desktop/ key to desktop-test/
FieldTypeDescription
Range?stringThe standard byte range, answered with 206 and a Content-Range header
StatusBodyCondition
200object bytesThe complete object was streamed
206object bytesThe requested byte range was streamed
302emptyThe deployment redirects this download
400error responseThe test value fails its schema and the request returns INVALID_FORM_BODY
404Not FoundThe key is outside the two permitted prefixes, the request has the /v1 prefix, or storage holds no such object
416emptyThe requested range is unsatisfiable

Fluxer also rejects the key when it is empty, when normalisation leaves it beginning .. or /, or when any segment is ., .., or contains a NUL character. Each of those returns the same 404, so a caller cannot tell a traversal attempt from a missing object.

A key can have the platform and architecture in one hyphenated segment, as in desktop/stable/linux-x64/manifest.json. Fluxer also tries that key with the segment split, as desktop/stable/linux/x64/manifest.json. It reads the requested form first and streams whichever of the two storage holds.

The 200 has Content-Type, Content-Disposition, Content-Length, Accept-Ranges: bytes, Cache-Control1, ETag2, and Last-Modified2. The 206 has the 200 headers plus Content-Range. The 302 has Location, Accept-Ranges: bytes, and Cache-Control. The 404 has Content-Type: text/plain. The 416 has Accept-Ranges: bytes, Content-Range: bytes */{size}, and Cache-Control.

1 public, max-age=31536000 for a released artifact, and public, max-age=300 for a release feed filename or a key under desktop-test/

2 Sent where storage reports the value