Skip to content

Upload Sessions ​

Preferred resumable upload API for modern Fangate clients.

Use upload sessions when you need:

  • direct-to-storage uploads
  • resumable multipart uploads
  • large video uploads
  • remote-import staging
  • an explicit backend-ready state before creating a product

After an upload session reaches ready, attach it to a product with POST /api/products using upload_session_id.


Session lifecycle ​

Device multipart flow ​

  1. POST /api/upload-sessions
  2. inspect upload_strategy
  3. if multipart, call POST /api/upload-sessions/{id}/parts for part instructions
  4. upload parts directly to storage
  5. POST /api/upload-sessions/{id}/complete
  6. poll GET /api/upload-sessions/{id}
  7. when status is ready, call POST /api/products with upload_session_id

Remote import flow ​

  1. POST /api/upload-sessions with session_type=remote_import
  2. POST /api/upload-sessions/{id}/remote with incoming file payload
  3. poll or finalize according to session state
  4. attach ready session to product

Uploading from your server or from a browser ​

The upload instructions are presigned storage URLs: whoever holds one can write exactly that file for 15 minutes, nothing else.

  • From your server (recommended). Your backend calls the API with the key, receives the instructions and sends the bytes. This works from any host without further setup.
  • From a browser on your own domain. The API calls work, but the browser only sends the bytes to storage if your site's origin is on the storage allow-list (CORS). An origin that is not listed fails with a generic network error although key and URL are valid. If a browser upload is the only option for your product, write to support (info@fangate.app) with the exact origin (for example https://app.example.com) and we add it. Alternatively hand the presigned URL to your own backend and let it send the bytes.

Keep the API key on your server in either case - a key placed in browser code can be read by every user of your application. Only the presigned URLs belong in the browser.


Session fields ​

Current session resources expose fields such as:

FieldTypeNotes
idstringUpload session id.
session_typestringdevice_multipart or remote_import.
statusstringcreated, uploading, uploaded, finalizing, ready, failed, aborted, expired.
upload_strategystringsingle_part or multipart.
asset_kindstring or nullUsually image or video.
file_namestring or nullOriginal filename.
file_size_bytesinteger or nullFile size in bytes.
mime_typestring or nullUploaded mime type.
staging_diskstringCurrent staging disk.
uploaded_partsarrayMultipart parts already accepted.
uploaded_assetobject or nullFinalized asset reference when ready.
initial_uploadobject or nullSingle-part upload instruction when applicable.

POST /api/upload-sessions ​

Create a new upload session.

  • Auth required: Yes
  • Roles: Authenticated creator
  • Content type: application/json

Request body ​

FieldTypeRequiredNotes
session_typestringYesdevice_multipart or remote_import.
product_idintegerNoExisting product the upload will later attach to.
asset_kindstringNoCurrent config supports image/video kinds.
file_namestringYes for device_multipartOriginal filename.
file_size_bytesintegerYes for device_multipartMax upload size is environment-configured, currently 7 GB.
mime_typestringYes for device_multipartMust be an allowed mime type.
remote_providerstringYes for remote_importMust be enabled in upload config.
remote_provider_file_idstringNoProvider-specific identifier.
remote_provider_metadataobjectNoProvider-specific metadata blob.
ai_originstringNoThe AI classification of the file this session will carry: none, synthetic, real_person_self, real_person_other. Sent here, it covers the later POST /api/media / POST /api/products call (a value on that call wins). See AI content.
ai_attestedbooleanWith AI valuesMust be true for every value other than none.
remote_attestationbooleanYes for remote_importRights and consent attestation for imported third-party material.

Allowed remote providers in current config ​

  • url
  • onedrive
  • unsplash
  • google_drive_picker
  • google_photos_picker
  • dropbox
  • box
  • google_drive

Success response example ​

json
{
  "success": true,
  "errors_message": null,
  "data": {
    "id": "01knyn6vkpnvh2rzgg9c0n6xz5",
    "session_type": "device_multipart",
    "status": "created",
    "upload_strategy": "multipart",
    "asset_kind": "video",
    "file_name": "sample.mp4",
    "file_size_bytes": 52428800,
    "mime_type": "video/mp4",
    "staging_disk": "uppy_staging_s3",
    "uploaded_parts": [],
    "uploaded_asset": null,
    "initial_upload": null
  }
}

Validation and side effects ​

  • if product_id is sent, ownership is verified
  • upload strategy is derived from configured multipart policy and file size
  • current default presign TTL is 15 minutes
  • current multipart threshold is 100 MB with an 8 MB minimum part size

GET /api/upload-sessions/ ​

Return the current state of one upload session.

  • Auth required: Yes
  • Roles: Session owner

Use this endpoint for polling after complete.


POST /api/upload-sessions/{uploadSession}/remote ​

Upload a remote-import payload into an existing remote session.

  • Auth required: Yes
  • Roles: Session owner
  • Content type: multipart/form-data

Request format ​

Send the file in either:

  • files[]
  • file

If the client sends X-Fangate-Upload-Session, it must match the route session id.


POST /api/upload-sessions/{uploadSession}/parts ​

Request a presigned instruction for one multipart part.

  • Auth required: Yes
  • Roles: Session owner
  • Content type: application/json

Request body ​

json
{
  "part_number": 1
}

Success response example ​

json
{
  "success": true,
  "errors_message": null,
  "data": {
    "session": {
      "id": "01knyn6vkpnvh2rzgg9c0n6xz5",
      "status": "uploading"
    },
    "part": {
      "part_number": 1,
      "method": "PUT",
      "url": "https://example-presigned-url",
      "headers": {
        "Content-Type": "video/mp4"
      },
      "expires_at": "2026-04-20T10:15:00Z"
    }
  }
}

POST /api/upload-sessions/{uploadSession}/complete ​

Finalize a direct upload.

  • Auth required: Yes
  • Roles: Session owner
  • Content type: application/json

Multipart request body ​

json
{
  "parts": [
    {
      "part_number": 1,
      "etag": "etag-value",
      "size_bytes": 8388608
    }
  ]
}

For single_part or remote_import flows, the body can be omitted.

Responses ​

  • 200 OK: session is already ready
  • 202 Accepted: finalization continues asynchronously

Side effects ​

  • may dispatch FinalizeUploadSessionJob
  • finalizer processes on filesystems.media_process_disk
  • if processing disk and final disk differ, backend mirrors the finalized asset to uploads.final_disk

DELETE /api/upload-sessions/ ​

Abort an unfinished upload session.

  • Auth required: Yes
  • Roles: Session owner

Returns the final session representation with status=aborted.


Accepted file types ​

What an upload session accepts - checked by the mime_type you declare when creating the session (422 mime_type_not_allowed_for_asset_kind otherwise), or for an imported file by its content type, falling back to the extension listed here. This is what you may send; Fangate re-encodes some files for playback on its own, which does not change what you can upload.

Images - up to 50 MB per file ​

mime_typeUsual extensions
image/jpeg.jpg, .jpeg
image/png.png
image/webp.webp
image/gif.gif
image/heic.heic
image/heif.heif

Video - up to 7 GB per file ​

mime_typeUsual extensions
video/mp4.mp4
video/quicktime.mov
video/x-msvideo.avi
video/x-matroska.mkv
video/webm.webm

Codecs are not checked up front. Any video with a readable video or audio track in one of these containers is taken (H.264, HEVC/H.265, VP8/VP9 and AV1 are the common ones). A file with no readable track at all - an interrupted recording or export, a split file, something that is not a media file - fails once during finalization: the session goes to status: failed with last_error_code: media_unreadable. Export the file again and start a new session; a retry of the same file gives the same answer.

The size limits are checked against the size actually uploaded at complete (422 upload_size_limit_exceeded).


Example flow: upload then create product ​

  1. POST /api/upload-sessions
  2. POST /api/upload-sessions/{id}/parts until all parts are uploaded
  3. POST /api/upload-sessions/{id}/complete
  4. GET /api/upload-sessions/{id} until status=ready
  5. POST /api/products with upload_session_id

Fangate backend developer documentation