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
| Header | Value |
|---|---|
Authorization | Bearer <jwt_token> |
Request Body
The request uses multipart/form-data format with the following fields:
| Field | Type | Required | Description |
|---|---|---|---|
file | file | ✅ | App binary file(s) |
app_name | string | ✅ | Name of the application |
version | string | ✅ | Version of the application |
channel | string | ❌ | Channel (e.g., stable, beta) |
publish | boolean | ❌ | Marks the version available for users |
critical | boolean | ❌ | Marks the version as critical |
platform | string | ❌ | Platform (e.g., linux, windows) |
arch | string | ❌ | Architecture (e.g., amd64, arm64) |
changelog | string | ❌ | Changelog in markdown format |
updater | string | ❌ | Updater type for artifact isolation (manual, velopack, sparkle, squirrel_darwin, squirrel_windows, electron-builder, tauri) |
signature | string | ❌ | Cryptographic signature (required for Tauri updater) |
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.
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
| Field | Type | Description |
|---|---|---|
uploadResult.Uploaded | string | The 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
| Updater | Generated Files | Use Case | Special Requirements |
|---|---|---|---|
manual | None | Standard FaynoSync uploads | None |
velopack | releases.{channel}.json | Velopack cross-platform desktop apps | Requires channel, platform, and arch |
sparkle | appcast.{channel}.xml | Native macOS apps using Sparkle | macOS only; upload the generate_appcast appcast + archives |
squirrel_darwin | None | Squirrel macOS applications | None |
squirrel_windows | RELEASES | Squirrel Windows applications | None |
electron-builder | *.yml | Electron applications | None |
tauri | None | Tauri applications with built-in updater | Requires signature field |
Notes
- You can upload multiple files for different platforms/architectures in a single request
- The
changelogfield supports markdown formatting - Critical versions are typically used for security updates or critical bug fixes
- Published versions are immediately available to end users
- The
channelfield helps organize releases (e.g., stable, beta, nightly) - File uploads support common package formats (.deb, .rpm, .exe, .dmg, etc.)
- Updater Parameter: Use the
updaterparameter 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
signaturefield 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,tauriandsquirrel_darwinupdaters.velopack,sparkle,electron-builderandsquirrel_windowsneed a publicly served update feed, so they are rejected with400: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 - 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
403withyou 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.