This guide covers uploading via single or multipart upload flows. Multipart is required for build files above 5GB, and optional for files smaller than this.

Authorization

Note, a Buildstash app-level API key is required for uploading builds.

Request to start

Request to start upload

Use /upload/request endpoint to pass in all key build information, request to begin the upload process, and receive a presigned URL for uploading the build file to.

/upload/request

Upload - Single part

If your primary build file is smaller than 5GB you have the option to upload as a single part.

Upload file to presigned URL

Upload the file
Request
  # Assuming you've parsed the values from previous `uploadRequest.data` into environment variables
curl -X PUT "$PRIMARY_FILE_URL" \
--upload-file "$PRIMARY_FILE_PATH" \
-H "Content-Type: $PRIMARY_FILE_CONTENT_TYPE" \
-H "Content-Length: $PRIMARY_FILE_CONTENT_LENGTH" \
-H "Content-Disposition: $PRIMARY_FILE_CONTENT_DISPOSITION" \
-H "x-amz-acl: private"
If the upload to S3 was successful, you’ll receive a response like:
/HTTP/1.1 200 OK
Date: Wed, 30 Jul 2025 20:48:28 GMT
Content-Type: text/plain;charset=UTF-8
Content-Length: 0
Connection: keep-alive
ETag: "a4d9c87f3e6b41c29fb2e7a8d1035b6e"
x-amz-checksum-crc64nvme: H1KdR+1sY8c=
x-amz-version-id: 93cf1f4e9e3d4ac2a8a4f929b03bd7a9
Server: cloudflare
CF-RAY: 9802ce3d5f6a1234-LAX
Note the response may vary slightly depending on your S3 provider, for example AWS S3, Cloudflare R2, etc.

Upload - Multipart

Alternatively, if your primary build file is larger than 5GB, you must upload as multiple parts. Your original upload request will also return a chunked_upload boolean indicating whether multipart upload is advised and available for this build.

Request to start upload

Use /upload/request/multipart endpoint to request the presigned URL for each part.

/upload/request/multipart

Upload part

For multipart uploads, once you have the presigned URL for each part, you’ll need to upload that part, store returned ETag, and upload the next part, until all are complete. The provided examples demonstrate a relatively simple approach to this. Note more advanced approaches are possible including parallel uploads for efficiency.
Request
# Set vars
FILE="yourfile.bin"
SIZE=$(stat -c %s "$FILE")
CHUNK_SIZE_MB=5
CHUNK_SIZE=$((CHUNK_SIZE_MB * 1024 * 1024))
API_KEY="your_api_key"
PENDING_ID="your_pending_upload_id"

# Upload loop
for i in $(seq 0 $(( (SIZE + CHUNK_SIZE - 1) / CHUNK_SIZE - 1))); do
  OFFSET=$((i * CHUNK_SIZE))
  PART_NUM=$((i + 1))
  LEN=$((CHUNK_SIZE))
  [ $((OFFSET + LEN)) -gt $SIZE ] && LEN=$((SIZE - OFFSET))

  echo "Uploading part $PART_NUM"

  PRESIGNED=$(curl -s -X POST https://app.buildstash.com/api/v1/upload/request/multipart \
    -H "Authorization: Bearer $API_KEY" \
    -H "Content-Type: application/json" \
    -d "{\"pending_upload_id\":\"$PENDING_ID\",\"part_number\":$PART_NUM,\"content_length\":$LEN}")

  URL=$(echo $PRESIGNED | jq -r .part_presigned_url)

  dd if="$FILE" bs=1 skip=$OFFSET count=$LEN 2>/dev/null | \
    curl -X PUT "$URL" -H "Content-Length: $LEN" -H "Content-Type: application/octet-stream" --data-binary @-
done
If the upload to S3 was successful, you’ll receive a response including the ETag for that part - which is the critical information for verifying the upload later.You’ll want to create an object pairing each part number with the accompanying ETag in order to verify the upload later.
HTTP/1.1 200 OK
x-amz-id-2: ...
x-amz-request-id: ...
ETag: "9b2cf535f27731c974343645a3985328"
Date: Wed, 21 Jul 2021 17:20:40 GMT
Content-Length: 0

Expansion upload

In addition to your primary build file, you may also optionally upload an expansion file, where this is supported by the target platform on Buildstash. An example of this is uploading an OBB expansion file to accompany a primary APK.

Include expansion file details in initial start upload request

When you call /upload/request endpoint in the first step, you’ll include the expansion file details there, and receive the presigned URL to upload to, similarly to with the primary file.

Upload expansion file

Upload your expansion file, using the same logic as with the primary file. Remember to not call /upload/verify until both primary and expansion files are uploaded.

Verify and complete

Verify and complete upload

Once all files are uploaded, use /upload/verify endpoint to inform Buildstash the upload is complete, validate all parts have successfully uploaded, and complete the upload flow. Once you receive a successful response from the endpoint, the build will be available in the Buildstash interface!

/upload/verify

Note, for some build types where additional server side processing is required (for example iOS and Android builds), the verify endpoint will return the pending_processing boolean as true. In this case there will be a small delay before the build is available for download.

Best practices

  1. Retry Logic: Implement retry logic for failed S3 file uploads
  2. Progress Tracking: Track upload progress for each part of large files
  3. Parallel Uploads: Upload parts in parallel for better performance
  4. Cleanup: Handle cleanup on upload failure

Provided integrations

CI Integrations

We provide a number of off-the-shelf integrations for uploading builds to Buildstash via widely used CI platforms - like GitHub Actions and Azure Pipelines.