Skip to main content

Upload Init (Presigned)

Starts a presigned upload. faynoSync validates the version metadata and the file manifest, stores the inline feed files, and returns one presigned PUT URL per file. The uploader then sends each file directly to object storage and finishes with Upload Complete.

All checks of a regular POST /upload run here, before any file is transferred.

Endpoint​

POST /upload/init

Authentication​

HeaderValue
AuthorizationBearer <jwt_token>

A CI/CD token works as well and must have access to the app.

Request Body​

multipart/form-data (or application/x-www-form-urlencoded when there are no inline feed files):

FieldTypeRequiredDescription
datastring (JSON)✅Version metadata — the same JSON as the data field of POST /upload: app_name, version, channel, publish, critical, platform, arch, changelog, updater, signature, rollout, ...
filesstring (JSON)✅Manifest of the files that will be uploaded through presigned URLs (see below)
filefile❌Updater feed files only (releases.{channel}.json, appcast*.xml, RELEASES, *.yml), sent inline. Max 10 MiB each

files manifest​

A JSON array with one entry per file:

[
{
"name": "MyApp-1.4.0-full.nupkg",
"md5": "fed304a43b43b889ac1b948e073928d0",
"sha256": "d73428d4e21289de53fa964274ed1dc0e39a4f58e3825fc69617c9d3dee366d7",
"sha512": "5a1b...(128 hex characters)",
"length": 52428800
}
]
FieldTypeRequiredDescription
namestring✅File name without any path. Must be unique within the upload
md5string✅Hex MD5 of the file (32 characters). Signed into the PUT URL; storage rejects bytes that do not match
sha256string✅ for TUF appsHex SHA-256 (64 characters). Verified against the stored bytes before TUF signing
sha512string✅ for TUF appsHex SHA-512 (128 characters). Signed into TUF targets together with sha256
lengthinteger❌Size in bytes, up to 5 GiB. When set, it is signed as Content-Length. The stored length always comes from object storage
Required for TUF apps

sha256 and sha512 are not checked by init, because only TUF publish uses them. For an app with TUF enabled they are effectively required: an artifact uploaded without them cannot be signed, and TUF clients will not be able to download it. For apps without TUF they can be omitted.

Digests are case-insensitive. Compute them from the same bytes you upload.

Example Request​

curl -X POST --location 'http://localhost:9000/upload/init' \
--header 'Authorization: Bearer <jwt_token>' \
--form-string 'data={"app_name":"myapp","version":"1.4.0","channel":"stable","publish":true,"platform":"windows","arch":"amd64","updater":"velopack"}' \
--form-string 'files=[{"name":"MyApp-1.4.0-full.nupkg","md5":"<md5>","sha256":"<sha256>","sha512":"<sha512>","length":52428800}]' \
--form 'file=@"/path_to_file/releases.stable.json"'

Use --form-string for data and files so that curl does not interpret @ or < inside the JSON. A complete script (hashing, init, PUT, complete) is on the Presigned Uploads page.

Response​

Success Response (200 OK)​

{
"upload_id": "f0a2a06eb2ab9e945eefe8e287ac41d9",
"expires_at": "2026-09-23T14:20:44.857939+03:00",
"files": [
{
"name": "MyApp-1.4.0-full.nupkg",
"method": "PUT",
"url": "https://my-bucket.s3.eu-central-1.amazonaws.com/pending/f0a2a06eb2ab9e945eefe8e287ac41d9/1?X-Amz-Algorithm=...",
"headers": {
"Content-Length": "52428800",
"Content-Md5": "cqfhk/D1vmKz3g30/HpOiQ=="
}
}
]
}

Response Fields​

FieldTypeDescription
upload_idstringIdentifier to pass to Upload Complete
expires_atstringWhen the presigned URLs expire (30 minutes after init by default, see PRESIGNED_UPLOAD_URL_TTL)
files[].namestringFile name from the manifest
files[].methodstringAlways PUT
files[].urlstringPresigned URL pointing directly at object storage
files[].headersobjectHeaders that must be sent with the PUT exactly as returned. They are part of the signature. Content-Length is present only when length was declared

Inline feed files are stored by faynoSync during init and are not listed in files.

Upload each file with:

curl -X PUT -H 'Content-Length: <...>' -H 'Content-Md5: <...>' --upload-file MyApp-1.4.0-full.nupkg '<url>'

Error Responses​

StatusWhen
400Invalid data or files; a file name with a path; duplicate names; a missing md5; a digest that is not hex of the right length; length above 5 GiB or negative; a feed file in the manifest or a non-feed file inline; updater file rules not met; an updater that private apps do not support; two files that would share an extension or storage key
401Missing or invalid token
403The CI/CD token has no access to the app, or the app, channel, platform or architecture is not in the team user's allowed lists
404The app does not exist
409An artifact with this app, version, platform, architecture and extension already exists, or the version already exists in another channel (the channel of a version cannot be changed)
501The configured storage driver does not support presigned uploads (MinIO). Use POST /upload

Notes​

  • Presigned URLs point at the storage API endpoint, which must be reachable from the machine doing the PUT.
  • Treat the URLs as credentials: they allow writing one staging object until they expire. Do not log them.
  • Files are staged under pending/<upload_id>/ and moved to their final keys by Upload Complete. Configure a lifecycle rule for abandoned uploads.
  • Storage-specific behavior is described in Storage provider differences.