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

# Product Delivery to DSPs

> Complete guide to delivering music products to Digital Service Providers (DSPs) using the Royalti.io API

## Overview

The Royalti.io API provides comprehensive functionality for delivering music products to Digital Service Providers (DSPs) like Spotify, Apple Music, YouTube Music, and more. This guide covers the complete delivery workflow, from preparation to monitoring.

### Key Features

* **Multi-Provider Delivery** - Deliver to multiple DSPs simultaneously
* **Real-Time Validation** - Pre-flight checks before delivery
* **Batch Operations** - Process up to 100 products at once
* **Status Monitoring** - Track delivery progress and logs
* **Automatic Retry** - Built-in retry logic for failed deliveries
* **Legacy Support** - Backward-compatible endpoints maintained

## Delivery Methods

The API provides two sets of delivery endpoints:

### New RESTful API (Recommended)

Modern, REST-compliant endpoints for product delivery with full CRUD operations:

* `/product/delivery-providers` - Get available providers
* `/product/{id}/deliveries` - Manage deliveries
* `/product/batch-delivery` - Batch operations

### Legacy API (Deprecated)

Maintained for backward compatibility:

* `/product/{id}/delivery` - Trigger delivery
* `/product/{id}/delivery/status` - Get status

<Warning>
  New integrations should use the RESTful API (`/deliveries`). Legacy endpoints will be deprecated in a future release.
</Warning>

## Prerequisites

Before delivering a product, ensure:

1. **Product has a valid UPC** - Required for all deliveries
2. **Assets have ISRCs** - Required for track-level metadata
3. **Metadata is complete** - Provider-specific requirements vary
4. **Provider is configured** - Tenant must have access to the provider

## Quick Start

<Steps>
  <Step title="Check Available Providers">
    Retrieve the list of providers available to your workspace:

    <Tabs>
      <Tab title="cURL">
        ```bash theme={null}
        curl -X GET https://api.royalti.io/product/delivery-providers \
          -H "Authorization: Bearer YOUR_API_TOKEN"
        ```
      </Tab>

      <Tab title="Node.js">
        ```javascript theme={null}
        const response = await fetch('https://api.royalti.io/product/delivery-providers', {
          headers: {
            'Authorization': `Bearer ${API_TOKEN}`
          }
        });

        const { data } = await response.json();
        console.log('Available providers:', data);
        ```
      </Tab>

      <Tab title="Python">
        ```python theme={null}
        import requests

        response = requests.get(
            'https://api.royalti.io/product/delivery-providers',
            headers={'Authorization': f'Bearer {API_TOKEN}'}
        )

        providers = response.json()['data']
        print(f'Available providers: {providers}')
        ```
      </Tab>
    </Tabs>

    <Note>
      Note the `requiredFields` for each provider - you'll need these for validation.
    </Note>
  </Step>

  <Step title="Validate Your Product">
    Before delivering, validate your product meets provider requirements:

    <Tabs>
      <Tab title="cURL">
        ```bash theme={null}
        curl -X POST https://api.royalti.io/product/prod-123/deliveries/validate \
          -H "Authorization: Bearer YOUR_API_TOKEN" \
          -H "Content-Type: application/json" \
          -d '{
            "providers": ["spotify-ddex-sftp", "apple-ddex-api"]
          }'
        ```
      </Tab>

      <Tab title="Node.js">
        ```javascript theme={null}
        const validation = await fetch('https://api.royalti.io/product/prod-123/deliveries/validate', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${API_TOKEN}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            providers: ['spotify-ddex-sftp', 'apple-ddex-api']
          })
        });

        const result = await validation.json();
        const isValid = result.data.validations.every(v => v.isValid);

        if (!isValid) {
          console.error('Validation failed:', result.data.validations);
        }
        ```
      </Tab>

      <Tab title="Python">
        ```python theme={null}
        response = requests.post(
            'https://api.royalti.io/product/prod-123/deliveries/validate',
            headers={
                'Authorization': f'Bearer {API_TOKEN}',
                'Content-Type': 'application/json'
            },
            json={
                'providers': ['spotify-ddex-sftp', 'apple-ddex-api']
            }
        )

        result = response.json()
        is_valid = all(v['isValid'] for v in result['data']['validations'])

        if not is_valid:
            print(f"Validation errors: {result['data']['validations']}")
        ```
      </Tab>
    </Tabs>

    <Tip>
      Validation is a dry-run - it checks requirements without actually delivering. Fix any errors before proceeding.
    </Tip>
  </Step>

  <Step title="Initiate Delivery">
    Once validation passes, initiate delivery to one or more DSPs:

    <Tabs>
      <Tab title="cURL">
        ```bash theme={null}
        curl -X POST https://api.royalti.io/product/prod-123/deliveries \
          -H "Authorization: Bearer YOUR_API_TOKEN" \
          -H "Content-Type: application/json" \
          -d '{
            "providers": ["spotify-ddex-sftp", "apple-ddex-api"],
            "autoDeliver": true,
            "settings": {
              "releaseType": "Album"
            }
          }'
        ```
      </Tab>

      <Tab title="Node.js">
        ```javascript theme={null}
        const delivery = await fetch('https://api.royalti.io/product/prod-123/deliveries', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${API_TOKEN}`,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({
            providers: ['spotify-ddex-sftp', 'apple-ddex-api'],
            autoDeliver: true,
            settings: {
              releaseType: 'Album'
            }
          })
        });

        const { data } = await delivery.json();
        console.log('Deliveries initiated:', data.deliveries);
        ```
      </Tab>

      <Tab title="Python">
        ```python theme={null}
        response = requests.post(
            'https://api.royalti.io/product/prod-123/deliveries',
            headers={
                'Authorization': f'Bearer {API_TOKEN}',
                'Content-Type': 'application/json'
            },
            json={
                'providers': ['spotify-ddex-sftp', 'apple-ddex-api'],
                'autoDeliver': True,
                'settings': {
                    'releaseType': 'Album'
                }
            }
        )

        data = response.json()['data']
        print(f"Deliveries initiated: {data['deliveries']}")
        ```
      </Tab>
    </Tabs>

    **Parameters:**

    * `providers` (required): Array of provider IDs or single provider string
    * `autoDeliver` (optional): Set to `false` for validation only (default: `true`)
    * `settings` (optional): Custom DDEX settings to override product defaults
  </Step>

  <Step title="Monitor Delivery Progress">
    Track delivery status and progress:

    <Tabs>
      <Tab title="cURL">
        ```bash theme={null}
        curl -X GET "https://api.royalti.io/product/prod-123/deliveries?status=processing" \
          -H "Authorization: Bearer YOUR_API_TOKEN"
        ```
      </Tab>

      <Tab title="Node.js">
        ```javascript theme={null}
        const status = await fetch('https://api.royalti.io/product/prod-123/deliveries?status=processing', {
          headers: {
            'Authorization': `Bearer ${API_TOKEN}`
          }
        });

        const { data } = await status.json();
        data.forEach(delivery => {
          console.log(`${delivery.provider}: ${delivery.status} (${delivery.progress}%)`);
        });
        ```
      </Tab>

      <Tab title="Python">
        ```python theme={null}
        response = requests.get(
            'https://api.royalti.io/product/prod-123/deliveries',
            headers={'Authorization': f'Bearer {API_TOKEN}'},
            params={'status': 'processing'}
        )

        deliveries = response.json()['data']
        for delivery in deliveries:
            print(f"{delivery['provider']}: {delivery['status']} ({delivery['progress']}%)")
        ```
      </Tab>
    </Tabs>

    **Query Parameters:**

    * `provider` - Filter by specific provider
    * `status` - Filter by delivery status
    * `deliveryId` - Get specific delivery
  </Step>
</Steps>

## Understanding Delivery Providers

Each provider has specific requirements and capabilities. Use the `/product/delivery-providers` endpoint to discover what's available.

**Response Example:**

```json theme={null}
{
  "status": "success",
  "message": "Available delivery providers retrieved from database",
  "data": [
    {
      "id": "spotify-ddex-sftp",
      "name": "Spotify (DDEX ERN)",
      "messageType": "ERN",
      "deliveryMethod": "SFTP",
      "requiredFields": {
        "product": ["title", "upc", "displayArtist", "releaseDate"],
        "asset": ["title", "isrc", "displayArtist"]
      },
      "requiredAssets": {
        "minimum": 1,
        "fields": []
      }
    }
  ]
}
```

### Provider Information

* **id**: Unique identifier for API calls
* **name**: Human-readable display name
* **messageType**: Format used (ERN, MEAD, CSV)
* **deliveryMethod**: Transport protocol (SFTP, FTP, API, HTTP)
* **requiredFields**: Mandatory metadata fields
* **requiredAssets**: Minimum asset requirements

## Delivery Status Lifecycle

Deliveries progress through the following statuses:

```mermaid theme={null}
graph LR
    A[pending] --> B[processing]
    B --> C[delivered]
    B --> D[failed]
    D --> E[retry]
    E --> B
    E --> F[cancelled]
```

**Status Definitions:**

* `pending` - Queued, not yet started
* `processing` - Actively being delivered
* `delivered` - Successfully completed
* `failed` - Delivery failed, can be retried
* `error` - System error occurred
* `rejected` - Provider rejected the delivery
* `cancelled` - Manually cancelled
* `retry` - Scheduled for retry

## Monitoring Delivery Status

Track delivery progress with detailed logs and status information:

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    # Get all deliveries for a product
    curl -X GET https://api.royalti.io/product/prod-123/deliveries \
      -H "Authorization: Bearer YOUR_API_TOKEN"

    # Get specific delivery details
    curl -X GET https://api.royalti.io/product/prod-123/deliveries/delivery-456 \
      -H "Authorization: Bearer YOUR_API_TOKEN"
    ```
  </Tab>

  <Tab title="Node.js">
    ```javascript theme={null}
    // Get all deliveries
    const response = await fetch(`https://api.royalti.io/product/${productId}/deliveries`, {
      headers: { 'Authorization': `Bearer ${API_TOKEN}` }
    });

    const { data } = await response.json();

    // Check delivery logs
    data.forEach(delivery => {
      console.log(`\nDelivery ${delivery.id}:`);
      console.log(`  Provider: ${delivery.providerName}`);
      console.log(`  Status: ${delivery.status}`);
      console.log(`  Progress: ${delivery.progress}%`);
      console.log(`  Attempts: ${delivery.attemptCount}/${delivery.maxAttempts}`);

      if (delivery.deliveryLog) {
        console.log('  Recent events:');
        delivery.deliveryLog.slice(-3).forEach(log => {
          console.log(`    - ${log.event} (${log.status})`);
        });
      }
    });
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    # Get all deliveries
    response = requests.get(
        f'https://api.royalti.io/product/{product_id}/deliveries',
        headers={'Authorization': f'Bearer {API_TOKEN}'}
    )

    deliveries = response.json()['data']

    # Check delivery logs
    for delivery in deliveries:
        print(f"\nDelivery {delivery['id']}:")
        print(f"  Provider: {delivery['providerName']}")
        print(f"  Status: {delivery['status']}")
        print(f"  Progress: {delivery['progress']}%")
        print(f"  Attempts: {delivery['attemptCount']}/{delivery['maxAttempts']}")

        if delivery.get('deliveryLog'):
            print("  Recent events:")
            for log in delivery['deliveryLog'][-3:]:
                print(f"    - {log['event']} ({log['status']})")
    ```
  </Tab>
</Tabs>

## Handling Failed Deliveries

If a delivery fails, you can retry it with the retry endpoint:

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X PUT https://api.royalti.io/product/prod-123/deliveries/delivery-456/retry \
      -H "Authorization: Bearer YOUR_API_TOKEN"
    ```
  </Tab>

  <Tab title="Node.js">
    ```javascript theme={null}
    const retry = await fetch(
      `https://api.royalti.io/product/${productId}/deliveries/${deliveryId}/retry`,
      {
        method: 'PUT',
        headers: { 'Authorization': `Bearer ${API_TOKEN}` }
      }
    );

    if (retry.ok) {
      const { data } = await retry.json();
      console.log(`Retry initiated. Attempt ${data.attemptCount}/${data.maxAttempts}`);
    }
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    response = requests.put(
        f'https://api.royalti.io/product/{product_id}/deliveries/{delivery_id}/retry',
        headers={'Authorization': f'Bearer {API_TOKEN}'}
    )

    if response.ok:
        data = response.json()['data']
        print(f"Retry initiated. Attempt {data['attemptCount']}/{data['maxAttempts']}")
    ```
  </Tab>
</Tabs>

**Retry Requirements:**

* Delivery must be in retryable status (`failed`, `error`, `rejected`, `cancelled`)
* Cannot exceed maximum retry attempts (default: 3)

## Cancelling Deliveries

Cancel a pending or in-progress delivery:

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X DELETE https://api.royalti.io/product/prod-123/deliveries/delivery-456 \
      -H "Authorization: Bearer YOUR_API_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"reason": "Product metadata needs updating"}'
    ```
  </Tab>

  <Tab title="Node.js">
    ```javascript theme={null}
    const cancel = await fetch(
      `https://api.royalti.io/product/${productId}/deliveries/${deliveryId}`,
      {
        method: 'DELETE',
        headers: {
          'Authorization': `Bearer ${API_TOKEN}`,
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({
          reason: 'Product metadata needs updating'
        })
      }
    );

    const { data } = await cancel.json();
    console.log(`Delivery cancelled: ${data.status}`);
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    response = requests.delete(
        f'https://api.royalti.io/product/{product_id}/deliveries/{delivery_id}',
        headers={
            'Authorization': f'Bearer {API_TOKEN}',
            'Content-Type': 'application/json'
        },
        json={'reason': 'Product metadata needs updating'}
    )

    data = response.json()['data']
    print(f"Delivery cancelled: {data['status']}")
    ```
  </Tab>
</Tabs>

**Cancellable Statuses:**

* `pending` - Not yet started
* `retry` - Scheduled for retry
* `processing` - Currently in progress

<Warning>
  Cancelling a delivery that's already completed on the provider side may require manual intervention with the DSP.
</Warning>

## Batch Delivery

Deliver multiple products to the same provider efficiently:

<Tabs>
  <Tab title="cURL">
    ```bash theme={null}
    curl -X POST https://api.royalti.io/product/batch-delivery \
      -H "Authorization: Bearer YOUR_API_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "productIds": ["prod-123", "prod-456", "prod-789"],
        "provider": "spotify-ddex-sftp",
        "priority": "normal",
        "settings": {
          "releaseType": "Album"
        }
      }'
    ```
  </Tab>

  <Tab title="Node.js">
    ```javascript theme={null}
    const batch = await fetch('https://api.royalti.io/product/batch-delivery', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${API_TOKEN}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        productIds: ['prod-123', 'prod-456', 'prod-789'],
        provider: 'spotify-ddex-sftp',
        priority: 'normal',
        settings: {
          releaseType: 'Album'
        }
      })
    });

    const { data } = await batch.json();
    console.log(`Batch ${data.batchId}: ${data.successfulDeliveries}/${data.totalProducts} successful`);
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    response = requests.post(
        'https://api.royalti.io/product/batch-delivery',
        headers={
            'Authorization': f'Bearer {API_TOKEN}',
            'Content-Type': 'application/json'
        },
        json={
            'productIds': ['prod-123', 'prod-456', 'prod-789'],
            'provider': 'spotify-ddex-sftp',
            'priority': 'normal',
            'settings': {
                'releaseType': 'Album'
            }
        }
    )

    data = response.json()['data']
    print(f"Batch {data['batchId']}: {data['successfulDeliveries']}/{data['totalProducts']} successful")
    ```
  </Tab>
</Tabs>

**Limitations:**

* Maximum 100 products per batch
* All products must have UPC codes
* All products delivered to same provider

**Response:**

```json theme={null}
{
  "status": "success",
  "message": "Batch delivery initiated successfully",
  "data": {
    "batchId": "batch_1234567890",
    "totalProducts": 3,
    "foundProducts": 3,
    "successfulDeliveries": 3,
    "failedDeliveries": 0,
    "provider": "spotify-ddex-sftp",
    "deliveries": [
      {
        "productId": "prod-123",
        "upc": "123456789012",
        "status": "queued",
        "deliveryId": "delivery-001"
      }
    ]
  }
}
```

## Common Use Cases

### Single Product, Multiple Providers

Deliver one product to multiple DSPs:

```bash theme={null}
POST /product/prod-123/deliveries
{
  "providers": [
    "spotify-ddex-sftp",
    "apple-ddex-api",
    "youtube-ddex-api"
  ]
}
```

### Validation Only (Dry-Run)

Check if product is ready without delivering:

```bash theme={null}
POST /product/prod-123/deliveries
{
  "providers": ["spotify-ddex-sftp"],
  "autoDeliver": false
}
```

### Custom DDEX Settings

Override default settings for specific delivery:

```bash theme={null}
POST /product/prod-123/deliveries
{
  "providers": ["spotify-ddex-sftp"],
  "settings": {
    "releaseType": "Single",
    "recordLabel": "Custom Label",
    "copyrightYear": 2024
  }
}
```

## Troubleshooting

<AccordionGroup>
  <Accordion title="Product must have a UPC to be delivered">
    **Error:** `400 Bad Request - Product must have a UPC to be delivered`

    **Cause:** The product doesn't have a UPC assigned, which is required for all DSP deliveries.

    **Solution:** Add a UPC to your product before attempting delivery:

    ```bash theme={null}
    curl -X PUT https://api.royalti.io/product/prod-123 \
      -H "Authorization: Bearer YOUR_API_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"upc": "123456789012"}'
    ```
  </Accordion>

  <Accordion title="Provider not available for tenant">
    **Error:** `Provider 'xyz-provider' not available for tenant`

    **Cause:** The provider you're trying to use isn't configured for your workspace, or the provider ID is incorrect.

    **Solution:**

    1. Check available providers for your workspace:
       ```bash theme={null}
       GET /product/delivery-providers
       ```
    2. Verify you're using the correct provider ID from the response
    3. Contact support if you need access to a specific provider
  </Accordion>

  <Accordion title="Missing required field errors">
    **Error:** Validation fails with missing field messages

    **Example:**

    ```json theme={null}
    {
      "provider": "spotify-ddex-sftp",
      "isValid": false,
      "errors": ["Missing required field: releaseDate"],
      "suggestions": {
        "releaseDate": "2024-09-01"
      }
    }
    ```

    **Solution:** The validation response includes specific field suggestions. Update your product with the missing data:

    ```bash theme={null}
    curl -X PUT https://api.royalti.io/product/prod-123 \
      -H "Authorization: Bearer YOUR_API_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{"releaseDate": "2024-09-01"}'
    ```
  </Accordion>

  <Accordion title="Product not found">
    **Error:** `404 Not Found - Product not found`

    **Cause:** The product ID doesn't exist or doesn't belong to your workspace.

    **Solution:**

    1. Verify the product ID is correct
    2. Check the product exists in your workspace:
       ```bash theme={null}
       GET /product/prod-123
       ```
    3. Ensure you're using the correct API token for your workspace
  </Accordion>

  <Accordion title="Delivery stuck in processing">
    **Issue:** Delivery status remains "processing" for extended period

    **Cause:** Large files, slow provider connections, or provider-side delays.

    **Solution:**

    1. Check delivery logs for detailed progress:
       ```bash theme={null}
       GET /product/prod-123/deliveries/delivery-456
       ```
    2. Typical delivery times:
       * Small releases (1-5 tracks): 5-15 minutes
       * Albums (6-20 tracks): 15-45 minutes
       * Large catalogs: 1-3 hours
    3. If stuck for more than expected time, contact support with the delivery ID
  </Accordion>

  <Accordion title="Maximum retry attempts reached">
    **Error:** `400 Bad Request - Delivery has reached maximum retry attempts (3)`

    **Cause:** The delivery has failed 3 times and can't be retried automatically.

    **Solution:**

    1. Review delivery logs to understand why it's failing:
       ```bash theme={null}
       GET /product/prod-123/deliveries/delivery-456
       ```
    2. Fix the underlying issue (usually metadata or file problems)
    3. Create a new delivery instead of retrying:
       ```bash theme={null}
       POST /product/prod-123/deliveries
       ```
  </Accordion>
</AccordionGroup>

## Best Practices

<CardGroup cols={2}>
  <Card title="Always Validate First" icon="check-circle">
    Run validation before delivery to catch issues early and avoid failed deliveries. This saves time and API quota.
  </Card>

  <Card title="Monitor Progress" icon="chart-line">
    Poll delivery status regularly to track progress and handle failures promptly. Set up webhooks for real-time notifications.
  </Card>

  <Card title="Use Batch Delivery" icon="layer-group">
    For multiple products, use batch delivery to reduce API calls and improve efficiency. Process up to 100 products at once.
  </Card>

  <Card title="Handle Retries" icon="rotate">
    Implement automatic retry logic for transient failures with exponential backoff. Don't retry immediately on failure.
  </Card>
</CardGroup>

## Complete Integration Example

Here's a full workflow example with error handling and monitoring:

<Tabs>
  <Tab title="Node.js">
    ```javascript theme={null}
    const axios = require('axios');

    async function deliverProduct(productId, providers) {
      const baseURL = 'https://api.royalti.io';
      const headers = {
        'Authorization': `Bearer ${API_TOKEN}`,
        'Content-Type': 'application/json'
      };

      try {
        // Step 1: Validate
        console.log('Validating product...');
        const validation = await axios.post(
          `${baseURL}/product/${productId}/deliveries/validate`,
          { providers },
          { headers }
        );

        if (!validation.data.data.validations.every(v => v.isValid)) {
          console.error('Validation failed:', validation.data);
          return;
        }

        // Step 2: Deliver
        console.log('Initiating delivery...');
        const delivery = await axios.post(
          `${baseURL}/product/${productId}/deliveries`,
          { providers, autoDeliver: true },
          { headers }
        );

        const deliveryIds = delivery.data.data.deliveries.map(d => d.deliveryId);
        console.log('Deliveries initiated:', deliveryIds);

        // Step 3: Monitor
        console.log('Monitoring delivery status...');
        for (const deliveryId of deliveryIds) {
          await monitorDelivery(productId, deliveryId);
        }

      } catch (error) {
        console.error('Delivery error:', error.response?.data || error.message);
      }
    }

    async function monitorDelivery(productId, deliveryId) {
      const baseURL = 'https://api.royalti.io';
      const headers = { 'Authorization': `Bearer ${API_TOKEN}` };

      let attempts = 0;
      const maxAttempts = 60; // 5 minutes with 5-second intervals

      while (attempts < maxAttempts) {
        const response = await axios.get(
          `${baseURL}/product/${productId}/deliveries/${deliveryId}`,
          { headers }
        );

        const { status, progress } = response.data.data;
        console.log(`Delivery ${deliveryId}: ${status} (${progress}%)`);

        if (status === 'delivered') {
          console.log('Delivery completed successfully!');
          return;
        }

        if (['failed', 'error', 'rejected'].includes(status)) {
          console.error('Delivery failed:', response.data.data);
          return;
        }

        await new Promise(resolve => setTimeout(resolve, 5000));
        attempts++;
      }

      console.warn('Monitoring timeout reached');
    }

    // Usage
    deliverProduct('prod-123', ['spotify-ddex-sftp', 'apple-ddex-api']);
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={null}
    import requests
    import time

    def deliver_product(product_id, providers):
        base_url = 'https://api.royalti.io'
        headers = {
            'Authorization': f'Bearer {API_TOKEN}',
            'Content-Type': 'application/json'
        }

        try:
            # Step 1: Validate
            print('Validating product...')
            validation = requests.post(
                f'{base_url}/product/{product_id}/deliveries/validate',
                headers=headers,
                json={'providers': providers}
            )
            validation.raise_for_status()

            if not all(v['isValid'] for v in validation.json()['data']['validations']):
                print(f'Validation failed: {validation.json()}')
                return

            # Step 2: Deliver
            print('Initiating delivery...')
            delivery = requests.post(
                f'{base_url}/product/{product_id}/deliveries',
                headers=headers,
                json={'providers': providers, 'autoDeliver': True}
            )
            delivery.raise_for_status()

            delivery_ids = [d['deliveryId'] for d in delivery.json()['data']['deliveries']]
            print(f'Deliveries initiated: {delivery_ids}')

            # Step 3: Monitor
            print('Monitoring delivery status...')
            for delivery_id in delivery_ids:
                monitor_delivery(product_id, delivery_id, headers, base_url)

        except requests.exceptions.RequestException as error:
            print(f'Delivery error: {error.response.json() if error.response else str(error)}')

    def monitor_delivery(product_id, delivery_id, headers, base_url):
        attempts = 0
        max_attempts = 60  # 5 minutes with 5-second intervals

        while attempts < max_attempts:
            response = requests.get(
                f'{base_url}/product/{product_id}/deliveries/{delivery_id}',
                headers=headers
            )
            response.raise_for_status()

            data = response.json()['data']
            status = data['status']
            progress = data['progress']

            print(f"Delivery {delivery_id}: {status} ({progress}%)")

            if status == 'delivered':
                print('Delivery completed successfully!')
                return

            if status in ['failed', 'error', 'rejected']:
                print(f'Delivery failed: {data}')
                return

            time.sleep(5)
            attempts += 1

        print('Monitoring timeout reached')

    # Usage
    deliver_product('prod-123', ['spotify-ddex-sftp', 'apple-ddex-api'])
    ```
  </Tab>
</Tabs>

## Related Resources

<CardGroup cols={2}>
  <Card title="API Reference" icon="book" href="/api-reference/fuga-delivery/post-product-id-deliveries">
    Complete API endpoint documentation
  </Card>

  <Card title="DDEX Integration" icon="file-code" href="/guides/ddex-integration">
    Learn about DDEX message generation
  </Card>

  <Card title="Asset DDEX Setup" icon="music" href="/guides/asset-ddex-management">
    Prepare assets for DDEX delivery
  </Card>

  <Card title="Release Management" icon="rocket" href="/guides/release-management">
    Automated product delivery via releases
  </Card>
</CardGroup>

## Support

Need help with product delivery?

* Check the [API Reference](/api-reference/product) for complete endpoint documentation
* Review [DDEX provider requirements](https://ddex.net/)
* Contact support at [api@royalti.io](mailto:api@royalti.io)
