> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fermion.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Downloadable Products over API

> Learn how to manage downloadable digital products over APIs

Fermion offers an easy-to-use API for creating and managing downloadable digital products. This guide will help you upload and publish ZIP files via API step by step.

## Overview

With Fermion's Downloadable Products API, you can:

* Create downloadable product entries through code
* Securely upload files to our cloud storage
* Process and prepare files for distribution
* Update product metadata and descriptions
* Set up and edit pricing plans
* Manage multiple versions of any downloadable product

## Prerequisites

Before you begin, make sure you have:

* A [valid Fermion API key](/api-guide/introduction#how-to-get-your-api-key)
* A ZIP file ready for upload
* Basic familiarity with HTTP requests and file uploads

<Steps>
  <Step title="Create a Downloadable Product">
    Create a new downloadable product using the `create-downloadable-product` endpoint.

    Refer to the [create-downloadable-product endpoint](/api-reference/product/create-downloadable-product) for call details.

    **Sample Response:**

    ```json theme={null}
    {
    	"status": "ok",
    	"data": {
    		"fermionDigitalProductId": "prod_abc123xyz",
    		"slug": "xyz789"
    	}
    }
    ```

    <Note>
      Save the `slug` value—it's a unique, URL-friendly ID you'll use for all later API calls.
    </Note>
  </Step>

  <Step title="Get a Presigned Upload URL">
    See the [get-presigned-url-for-downloadable-product-upload](/api-reference/product/get-presigned-url-for-downloadable-product-upload) for details.

    **Sample Response:**

    ```json theme={null}
    {
    	"status": "ok",
    	"data": {
    		"r2KeyForOriginalDownloadableProduct": "private-cached-downloadable-unencrypted/prod_abc123xyz/file_unique_id",
    		"presignedUrl": "https://storage.example.com/upload?signature=..."
    	}
    }
    ```

    <Warning>
      The presigned upload URL will expire in **1 hour**. Be sure to upload your file before then.
      If it expires, just request a new upload URL.
    </Warning>

    **Key Points:**

    * Save `r2KeyForOriginalDownloadableProduct`—you'll need it next
    * The upload link is one-time and time-limited for security
    * Requesting a new URL allows you to upload a new version
  </Step>

  <Step title="Upload the File via Presigned URL">
    Upload your file using a HTTP PUT request to the URL from Step 2.

    <CodeGroup>
      ```bash cURL theme={null}
      curl -X PUT \
        -H "Content-Type: application/zip" \
        --data-binary @your-product.zip \
        "https://storage.example.com/upload?signature=..."
      ```

      ```javascript JavaScript (Node.js) theme={null}
      const fs = require('fs')

      const fileBuffer = fs.readFileSync('your-product.zip')

      await fetch(presignedUrl, {
      	method: 'PUT',
      	headers: {
      		'Content-Type': 'application/zip'
      	},
      	body: fileBuffer
      })
      ```

      ```python Python theme={null}
      import requests

      with open('your-product.zip', 'rb') as f:
          file_data = f.read()

      response = requests.put(
          presigned_url,
          headers={'Content-Type': 'application/zip'},
          data=file_data
      )
      ```
    </CodeGroup>

    <Tip>
      Use the HTTP PUT method (not POST) and make sure <code>Content-Type</code> is set to <code>application/zip</code> for correct uploads.
    </Tip>
  </Step>

  <Step title="Process the Uploaded Product">
    After uploading your file, trigger processing to prepare it for distribution.

    See the [process-uploaded-downloadable-product](/api-reference/product/process-uploaded-downloadable-product).

    **Sample Request Body:**

    ```json theme={null}
    {
    	"productSlug": "xyz789",
    	"r2KeyForOriginalDownloadableProduct": "private-cached-downloadable-unencrypted/prod_abc123xyz/file_unique_id"
    }
    ```

    **Sample Response:**

    ```json theme={null}
    {
    	"status": "ok",
    	"data": {
    		"downloadableProductVersionIndex": 0
    	}
    }
    ```

    **What Processing Does:**

    1. Downloads your file from storage
    2. Validates the file format and integrity
    3. Prepares the file for secure distribution
    4. Logs version metadata to your product

    <Info>
      Save the `downloadableProductVersionIndex`—you'll need it to activate (publish) this version.
    </Info>
  </Step>

  <Step title="Update Product Metadata (Optional)">
    See API docs: [update-downloadable-product-metadata](/api-reference/product/update-downloadable-product-metadata)

    **Sample Request Body:**

    ```json theme={null}
    {
    	"productSlug": "xyz789",
    	"title": "Premium Design Templates Pack",
    	"shortDescription": "Professional templates for web and print",
    	"longDescription": "This comprehensive pack includes everything you need to create stunning designs...",
    	"activeVersionIndex": 0
    }
    ```

    **Field Breakdown:**

    * `title` (optional): Product display title
    * `shortDescription` (optional): Summary for listings
    * `longDescription` (optional): Main description on product page
    * `activeVersionIndex` (optional): Which uploaded version customers see

    <Warning>
      **Important:** Set `activeVersionIndex` to the version index returned from Step 4 to activate your upload.
      Only processed downloadable product versions (`uploaded-and-processed`) can be marked active.
    </Warning>
  </Step>

  <Step title="Add Pricing (Optional)">
    To set up paid access, use the digital product pricing API.

    See docs: [create-custom-pricing-plan-for-a-product](/api-reference/product/create-custom-pricing-plan-for-a-product)
  </Step>

  <Step title="Publish & Share">
    After completing the above, your downloadable product is ready to be published by setting it to "Active."
  </Step>
</Steps>

## Complete Example: JavaScript/Node.js Workflow

Below is a full downloadable product upload example using Node.js:

```javascript theme={null}
const API_KEY = 'your-api-key'
const BASE_URL = 'https://your-school.fermion.app/api'

// Step 1: Create the downloadable product
const createResponse = await fetch(`${BASE_URL}/public/create-downloadable-product`, {
	method: 'POST',
	headers: {
		'fermion-api-key': API_KEY,
		'Content-Type': 'application/json'
	},
	body: JSON.stringify({
		downloadableProductName: 'My Premium Templates',
		shortDescription: 'Professional design templates',
		status: 'Draft'
	})
})

const { slug, fermionDigitalProductId } = (await createResponse.json()).data

// Step 2: Request presigned upload URL
const uploadUrlResponse = await fetch(`${BASE_URL}/public/get-presigned-url-for-downloadable-product-upload`, {
	method: 'POST',
	headers: {
		'fermion-api-key': API_KEY,
		'Content-Type': 'application/json'
	},
	body: JSON.stringify({ productSlug: slug })
})

const { presignedUrl, r2KeyForOriginalDownloadableProduct } = (await uploadUrlResponse.json()).data

// Step 3: Upload the file
const fileBuffer = fs.readFileSync('./my-product.zip')
await fetch(presignedUrl, {
	method: 'PUT',
	headers: { 'Content-Type': 'application/zip' },
	body: fileBuffer
})

// Step 4: Process the uploaded file
const processResponse = await fetch(`${BASE_URL}/public/process-uploaded-downloadable-product`, {
	method: 'POST',
	headers: {
		'fermion-api-key': API_KEY,
		'Content-Type': 'application/json'
	},
	body: JSON.stringify({
		productSlug: slug,
		r2KeyForOriginalDownloadableProduct
	})
})

const { downloadableProductVersionIndex } = (await processResponse.json()).data

// Step 5: Update metadata and activate version
await fetch(`${BASE_URL}/public/update-downloadable-product-metadata`, {
	method: 'POST',
	headers: {
		'fermion-api-key': API_KEY,
		'Content-Type': 'application/json'
	},
	body: JSON.stringify({
		productSlug: slug,
		title: 'My Premium Templates',
		longDescription: 'This pack includes professional templates...',
		activeVersionIndex: downloadableProductVersionIndex
	})
})

console.log(`Downloadable product created successfully! Product ID: ${fermionDigitalProductId}`)
```

## Managing Multiple Product Versions

You can upload new versions (such as updates or corrections) for the same downloadable product:

<Steps>
  <Step title="Get New Presigned Upload URL">
    Call <code>get-presigned-url-for-downloadable-product-upload</code> again to get a fresh upload URL.
  </Step>

  <Step title="Upload New File">
    Upload your new file via the new URL.
  </Step>

  <Step title="Process the Upload">
    Process the new version.
  </Step>

  <Step title="Update Version Index">
    Update <code>activeVersionIndex</code> via the metadata API to switch to the new version.
  </Step>
</Steps>

<Tip>
  Don't delete past versions—you can switch between them any time by changing <code>activeVersionIndex</code>.
</Tip>

## Related Guides

* Learn about [eBooks over API](/api-guide/ebooks-over-api) for PDF-based digital products
* Explore [affiliate marketing over API](/api-guide/affiliate-marketing-over-api) to promote your products
* Check out [user endpoints](/api-guide/using-user-endpoints) for managing customer enrollments

## Getting Help

If you get stuck or have an edge-case issue:

* See the [API Reference](https://docs.fermion.app/api-reference) for endpoint details
* Check the [Webhooks Guide](https://docs.fermion.app/webhooks) to track product purchases
* For product-specific issues, contact support and include your `fermionDigitalProductId`
