ScripTreeApps / Developer Portal

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:

  1. POST /api/v1/producer/bundles/upload-init — obtain a pre-signed upload URL
  2. PUT to the upload URL — upload your bundle directly to object storage (not through our servers)
  3. 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_KEY

To 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:

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:

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:

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:

Response (200 OK)

{
  "bundle_status": "QUARANTINE_RECEIVED",
  "scan_queued": true
}

Fields:

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

StatusMeaning
200Request succeeded
400Bad request (missing field, invalid SHA, size mismatch)
401Unauthorized (missing/invalid API key)
403Forbidden (you don't own this app or producer ID)
409Conflict (bundle in wrong state for finalize)
500Server error (rare; retry with exponential backoff)

After you finalize

Once you call finalize, ${brand.name} will:

  1. Verify the SHA-256 matches what you declared
  2. Scan your LICENSE.md and BUNDLED.md files for license metadata
  3. 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.