Producer Bundles API
The Producer Bundles API lets you upload app bundles and finalize uploads without relying on the ${brand.name} CI runner. This is useful if you're building a custom CI/CD pipeline or handling bundle uploads from an external build system.
Overview
The typical workflow is:
- POST /api/v1/producer/bundles/upload-init — obtain a pre-signed upload URL
- PUT to the upload URL — upload your bundle directly to object storage (not through our servers)
- POST /api/v1/producer/bundles/{bundleId}/finalize — confirm the upload and queue license scanning
Authentication
Both endpoints require Bearer token authentication using your producer API key. Include it in the Authorization header:
Authorization: Bearer YOUR_PRODUCER_API_KEYTo find your API key, log into the developer portal, go to Account Settings > API Keys, and copy your key. Keep it secret — treat it like a password.
POST /api/v1/producer/bundles/upload-init
Mint a pre-signed upload URL and create a bundle record.
Request
Content-Type: application/json
{
"producer_id": "PROD_001",
"app_id": "APP_001",
"version_string": "1.2.3",
"bundle_size_bytes": 5242880,
"bundle_sha256": "a1b2c3d4e5f6...6364"
}Fields:
producer_id(string, required) — your producer ID (shown in Account Settings)app_id(string, required) — the app ID you're uploading a new version forversion_string(string, required) — the version tag (e.g., "1.2.3"). Must match your Git tagv1.2.3(without the leading 'v')bundle_size_bytes(integer, required) — the exact byte size of your .scriptree bundlebundle_sha256(string, required) — the SHA-256 hash of your .scriptree file (hex, lowercase, 64 characters)
Response (200 OK)
{
"upload_url": "https://minio.example.com/dev-quarantine/quarantine/PROD_001/APP_001/1.2.3/...?X-Amz-Signature=...",
"bundle_id": "550e8400-e29b-41d4-a716-446655440000",
"object_key": "quarantine/PROD_001/APP_001/1.2.3/550e8400-e29b-41d4-a716-446655440000.scriptree",
"expires_at": "2026-05-08T10:15:00Z"
}Fields:
upload_url— pre-signed HTTPS URL where you PUT the bundle. Valid for 24 hours.bundle_id— unique ID for this bundle upload. Use it in the finalize step.object_key— the MinIO object key. For reference only.expires_at— ISO-8601 timestamp when the upload URL expires.
Example
curl -X POST https://{brand.primary_domain}/api/v1/producer/bundles/upload-init \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"producer_id": "PROD_001",
"app_id": "APP_001",
"version_string": "1.2.3",
"bundle_size_bytes": 5242880,
"bundle_sha256": "a1b2c3d4e5f6..."
}'PUT to the upload URL (direct to object storage)
Use the upload_url from the response above to upload your.scriptree file directly. This is a standard HTTPS PUT request to MinIO.
Request
curl -X PUT "https://minio.example.com/dev-quarantine/..." \
--data-binary @bundle.scriptree \
-H "Content-Type: application/zip"Important:
- Use
--data-binary(not-d) to upload the file as binary - The URL is pre-signed — do NOT include any authentication headers
- Do NOT modify the URL or its query parameters
- The upload must complete before the URL expires (24 hours)
POST /api/v1/producer/bundles/{bundleId}/finalize
Confirm the bundle upload is complete and queue license disclosure scanning.
Request
Content-Type: application/json
{
"bundle_id": "550e8400-e29b-41d4-a716-446655440000",
"actual_sha256": "a1b2c3d4e5f6...6364"
}Fields:
bundle_id(string, required) — the bundle ID from upload-init responseactual_sha256(string, required) — the SHA-256 hash of the file you just uploaded. Must match thebundle_sha256from upload-init.
Response (200 OK)
{
"bundle_status": "QUARANTINE_RECEIVED",
"scan_queued": true
}Fields:
bundle_status— current state of the bundle (QUARANTINE_RECEIVED = uploaded and ready for scanning)scan_queued— true if the license-disclosure scan has been queued
Idempotency
Calling finalize on a bundle that's already been finalized (or further along in the pipeline) returns the current status without error. Safe to retry.
Example
curl -X POST "https://{brand.primary_domain}/api/v1/producer/bundles/550e8400-e29b-41d4-a716-446655440000/finalize" \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"bundle_id": "550e8400-e29b-41d4-a716-446655440000",
"actual_sha256": "a1b2c3d4e5f6..."
}'Complete workflow example
#!/bin/bash
# Build and upload your bundle
PRODUCER_ID="PROD_001"
APP_ID="APP_001"
VERSION="1.2.3"
API_KEY="your_api_key_here"
BASE_URL="https://{brand.primary_domain}"
# 1. Compute SHA-256 of your built bundle
BUNDLE_FILE="dist/your-app-1.2.3.scriptree"
BUNDLE_SHA=$(sha256sum "$BUNDLE_FILE" | awk '{print $1}')
BUNDLE_SIZE=$(stat --format=%s "$BUNDLE_FILE")
# 2. Mint upload URL
UPLOAD_INIT=$(curl -s -X POST "$BASE_URL/api/v1/producer/bundles/upload-init" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d "{
"producer_id": "$PRODUCER_ID",
"app_id": "$APP_ID",
"version_string": "$VERSION",
"bundle_size_bytes": $BUNDLE_SIZE,
"bundle_sha256": "$BUNDLE_SHA"
}")
UPLOAD_URL=$(echo "$UPLOAD_INIT" | jq -r '.upload_url')
BUNDLE_ID=$(echo "$UPLOAD_INIT" | jq -r '.bundle_id')
echo "Upload URL: $UPLOAD_URL"
echo "Bundle ID: $BUNDLE_ID"
# 3. Upload the bundle
curl -X PUT "$UPLOAD_URL" \
--data-binary @"$BUNDLE_FILE" \
-H "Content-Type: application/zip"
# 4. Finalize
curl -X POST "$BASE_URL/api/v1/producer/bundles/$BUNDLE_ID/finalize" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d "{
"bundle_id": "$BUNDLE_ID",
"actual_sha256": "$BUNDLE_SHA"
}"
echo "Bundle uploaded and scan queued!"
Error codes
| Status | Meaning |
|---|---|
200 | Request succeeded |
400 | Bad request (missing field, invalid SHA, size mismatch) |
401 | Unauthorized (missing/invalid API key) |
403 | Forbidden (you don't own this app or producer ID) |
409 | Conflict (bundle in wrong state for finalize) |
500 | Server error (rare; retry with exponential backoff) |
After you finalize
Once you call finalize, ${brand.name} will:
- Verify the SHA-256 matches what you declared
- Scan your
LICENSE.mdandBUNDLED.mdfiles for license metadata - Queue your bundle for operator review and signing
Log into the developer portal and go to your listing to review the license disclosure form, just as you would with the CI runner. You'll see the same auto-detected values and can edit anything before confirming.
Most producers use the CI runner
The built-in CI runner handles all of this automatically when you push a Git tag. Direct API access is useful if you have a custom build pipeline or need finer control. For the typical case, see the Publishing guide.
Need help?
Contact our support team if you run into issues with the API.