Skip to main content

Upload App

Upload a new version of an application, including the binary file and relevant metadata. You can specify an updater type for artifact isolation when uploading files for updaters that generate special files.

Endpoint​

POST /upload

Authentication​

HeaderValue
AuthorizationBearer <jwt_token>

Request Body​

The request uses multipart/form-data format with the following fields:

FieldTypeRequiredDescription
filefile✅App binary file(s)
app_namestring✅Name of the application
versionstring✅Version of the application
channelstring❌Channel (e.g., stable, beta)
publishboolean❌Marks the version available for users
criticalboolean❌Marks the version as critical
platformstring❌Platform (e.g., linux, windows)
archstring❌Architecture (e.g., amd64, arm64)
changelogstring❌Changelog in markdown format
updaterstring❌Updater type for artifact isolation (manual, velopack, sparkle, squirrel_darwin, squirrel_windows, electron-builder, tauri)
signaturestring❌Cryptographic signature (required for Tauri updater)
Large files

Every file sent to this endpoint passes through faynoSync and the reverse proxy in front of it. For large builds, use presigned uploads (Upload Init + Upload Complete) to send files directly to object storage.

info

The updater parameter is useful when uploading files for updaters that generate or ingest special files (like RELEASES for Squirrel Windows, *.yml for electron-builder, releases.{channel}.json for Velopack, or appcast.{channel}.xml for Sparkle). It ensures files are stored in isolated folders specific to that updater.

Example Requests​

Single File Upload (Standard)​

curl -X POST --location 'http://localhost:9000/upload' \
--header 'Authorization: Bearer <jwt_token>' \
--form 'file=@"/path_to_file/myapp.deb"' \
--form 'data="{\"app_name\":\"myapp\",\"version\":\"0.0.1\",\"channel\":\"stable\",\"publish\":true,\"platform\":\"linux\",\"arch\":\"amd64\",\"changelog\":\"### Changelog\\n\\n- Added new feature X\\n- Fixed bug Y\"}"'

Upload with Updater Parameter (Squirrel Windows)​

curl -X POST --location 'http://localhost:9000/upload' \
--header 'Authorization: Bearer <jwt_token>' \
--form 'file=@"/path_to_file/myapp.exe"' \
--form 'file=@"/path_to_file/RELEASES"' \
--form 'data="{\"app_name\":\"myapp\",\"version\":\"0.0.1\",\"channel\":\"stable\",\"publish\":true,\"platform\":\"windows\",\"arch\":\"amd64\",\"updater\":\"squirrel_windows\",\"changelog\":\"### Changelog\\n\\n- Added new feature X\\n- Fixed bug Y\"}"'

Upload with Updater Parameter (Electron Builder)​

curl -X POST --location 'http://localhost:9000/upload' \
--header 'Authorization: Bearer <jwt_token>' \
--form 'file=@"/path_to_file/myapp.exe"' \
--form 'file=@"/path_to_file/latest.yml"' \
--form 'data="{\"app_name\":\"myapp\",\"version\":\"0.0.1\",\"channel\":\"stable\",\"publish\":true,\"platform\":\"windows\",\"arch\":\"amd64\",\"updater\":\"electron-builder\",\"changelog\":\"### Changelog\\n\\n- Added new feature X\\n- Fixed bug Y\"}"'

Upload with Updater Parameter (Tauri)​

curl -X POST --location 'http://localhost:9000/upload' \
--header 'Authorization: Bearer <jwt_token>' \
--form 'file=@"/path_to_file/myapp.app.tar.gz"' \
--form 'data="{\"app_name\":\"myapp\",\"version\":\"1.0.0\",\"channel\":\"stable\",\"publish\":true,\"platform\":\"darwin\",\"arch\":\"amd64\",\"updater\":\"tauri\",\"signature\":\"dW50cnVzdGVkIGNvbW1lbnQ6IHNpZ25hdHVyZSBmcm9tIHRhdXJpIHNlY3JldCBrZXkKUlVUUzIzUGJLcHpQNHdCVEJzTXJjbWFhUUdUVEtaUGRLdGhWdGZJZkF2VmM0TzJGMkdPQUk4V1hzYWJuUUhWbWI2cTFWTkhEZE9lbkVwUERCQmhRPT0K\",\"changelog\":\"### Changelog\\n\\n- Added new feature X\\n- Fixed bug Y\"}"'

Upload with Updater Parameter (Sparkle, macOS)​

Sparkle has no signature field — the edSignature already lives inside the uploaded appcast. Upload the generate_appcast output plus every archive/delta it references:

curl -X POST --location 'http://localhost:9000/upload' \
--header 'Authorization: Bearer <jwt_token>' \
--form 'file=@"/path_to_file/appcast.stable.xml"' \
--form 'file=@"/path_to_file/MyApp-1.0.0.zip"' \
--form 'data="{\"app_name\":\"myapp\",\"version\":\"1.0.0\",\"channel\":\"stable\",\"publish\":true,\"platform\":\"darwin\",\"arch\":\"arm64\",\"updater\":\"sparkle\",\"changelog\":\"### Changelog\\n\\n- Added new feature X\\n- Fixed bug Y\"}"'

Multiple File Upload (Standard)​

curl -X POST --location 'http://localhost:9000/upload' \
--header 'Authorization: Bearer <jwt_token>' \
--form 'file=@"/path_to_file/myapp.deb"' \
--form 'file=@"/path_to_file/myapp.rpm"' \
--form 'data="{\"app_name\":\"myapp\",\"version\":\"0.0.1\",\"channel\":\"stable\",\"publish\":true,\"platform\":\"linux\",\"arch\":\"amd64\",\"changelog\":\"### Changelog\\n\\n- Added new feature X\\n- Fixed bug Y\"}"'

Response​

Success Response (200 OK)​

{
"uploadResult.Uploaded": "6411c7c0ec4ff9a9a9bc18fa"
}

Response Fields​

FieldTypeDescription
uploadResult.UploadedstringThe unique identifier (ID) of the uploaded version

Updater-Specific File Organization​

When you specify an updater parameter, files are organized in isolated folders:

File Structure Examples​

Standard Upload (No Updater):

/myapp/stable/linux/amd64/
├── myapp-0.0.1.deb
└── myapp-0.0.1.rpm

Squirrel Windows Upload:

/squirrel_windows/myapp/stable/windows/amd64/0.0.1/
├── myapp.exe
└── RELEASES

Electron Builder Upload:

/electron-builder/myapp/stable/windows/amd64/0.0.1/
├── myapp.exe
└── latest.yml

Sparkle Upload (materialized per platform/arch):

/sparkle/admin/myapp/darwin/arm64/
├── MyApp-1.0.0.zip
└── appcast.stable.xml

Supported Updater Types​

UpdaterGenerated FilesUse CaseSpecial Requirements
manualNoneStandard FaynoSync uploadsNone
velopackreleases.{channel}.jsonVelopack cross-platform desktop appsRequires channel, platform, and arch
sparkleappcast.{channel}.xmlNative macOS apps using SparklemacOS only; upload the generate_appcast appcast + archives
squirrel_darwinNoneSquirrel macOS applicationsNone
squirrel_windowsRELEASESSquirrel Windows applicationsNone
electron-builder*.ymlElectron applicationsNone
tauriNoneTauri applications with built-in updaterRequires signature field

Notes​

  • You can upload multiple files for different platforms/architectures in a single request
  • The changelog field supports markdown formatting
  • Critical versions are typically used for security updates or critical bug fixes
  • Published versions are immediately available to end users
  • The channel field helps organize releases (e.g., stable, beta, nightly)
  • File uploads support common package formats (.deb, .rpm, .exe, .dmg, etc.)
  • Updater Parameter: Use the updater parameter when uploading files for updaters that generate special files (RELEASES, *.yml)
  • Artifact Isolation: Files uploaded with an updater parameter are stored in isolated folders to prevent conflicts
  • File Organization: Generated files (RELEASES, *.yml) are properly organized when using updater-specific uploads
  • Signature Field: When using Tauri updater, the signature field is required for cryptographic verification of updates. This signature is generated by Tauri's build process and ensures update integrity and authenticity.
  • Private Apps: a private app accepts only the manual, tauri and squirrel_darwin updaters. velopack, sparkle, electron-builder and squirrel_windows need a publicly served update feed, so they are rejected with 400: updater is not supported for private apps: <updater> requires a publicly served update feed; use manual, tauri or squirrel_darwin, or a public app
  • Uploading to an app that does not exist returns 404
  • Conflicts are checked before anything is written. If two files of the request would share a storage key or an extension, the request returns 400 (files "<a>" and "<b>" would be stored under the same key / ... have the same extension). If the version already has an artifact with the same platform, architecture and extension, it returns 409 with app with this name, version, platform, architecture and extension already exists; if the version exists in another channel, 409 with this version already exists in another channel; the channel of a version cannot be changed (the same codes as presigned uploads). Before this change both conflicts returned 500. In both cases no file is written to storage and no artifact is recorded, so a stored file is never overwritten and a retry never meets a half-recorded version. .nupkg, .delta and .blockmap may repeat within one platform and architecture (full and delta packages, the .zip.blockmap and .dmg.blockmap of an electron-builder macOS build) as long as each is stored under its own key. Without an updater, files are stored as {app}-{version}{ext}, so a second .nupkg, .delta or .blockmap for the same version, platform and architecture also returns 409
  • Team users: the app, channel, platform and architecture must all be in the team user's allowed lists (see Team Based Authorization). Otherwise the request returns 403 with you don't have access to this <app|channel|platform|architecture>, and no file is written to storage. Before v2.4.0 these lists were not checked on this endpoint.