> ## 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.

# Royalty Management Guide

> Upload, process, and analyze royalty data from multiple streaming platforms and distributors

## Overview

The Royalti.io API enables you to upload royalty statements from streaming platforms and distributors, automatically distribute earnings based on configured splits, and access comprehensive analytics. This guide covers the complete royalty workflow from file upload to payment distribution.

### Key Features

* **Multi-format upload** - Support for CSV, Excel (XLS/XLSX), and ZIP archives
* **Automatic source detection** - Recognizes major DSPs and distributors
* **Split-based distribution** - Earnings calculated per configured revenue shares
* **Comprehensive analytics** - Track performance by artist, territory, platform, and time
* **Period management** - Organize royalties by accounting and sales periods

## Prerequisites

### Required Data

Before uploading royalty files, ensure you have:

* **Configured splits** - Revenue shares assigned to users for assets/products
* **Catalog items** - Assets and products created in the system
* **User accounts** - Collaborators who will receive earnings

### Authentication

<Note>
  All royalty endpoints require authentication with a Bearer token.
</Note>

```javascript Node.js theme={null}
const token = 'your_jwt_token';
const headers = {
  'Authorization': `Bearer ${token}`,
  'Content-Type': 'application/json'
};
```

```python Python theme={null}
import requests

token = 'your_jwt_token'
headers = {
    'Authorization': f'Bearer {token}',
    'Content-Type': 'application/json'
}
```

## Quick Start: Upload Your First Royalty File

<Steps>
  <Step title="Request Upload URL">
    Get a secure upload URL for your royalty file.

    ```javascript Node.js theme={null}
    const response = await fetch('https://api.royalti.io/file/upload-url', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        royaltySource: 'spotify',
        accountingPeriod: '2024-01-01',
        salePeriod: '2024-01-01',
        fileMetaData: {
          fileName: 'spotify-royalties-jan-2024.csv',
          fileSize: 1024000,
          mimeType: 'text/csv'
        }
      })
    });

    const { uploadUrl, fileId } = await response.json();
    ```

    ```python Python theme={null}
    response = requests.post(
        'https://api.royalti.io/file/upload-url',
        headers=headers,
        json={
            'royaltySource': 'spotify',
            'accountingPeriod': '2024-01-01',
            'salePeriod': '2024-01-01',
            'fileMetaData': {
                'fileName': 'spotify-royalties-jan-2024.csv',
                'fileSize': 1024000,
                'mimeType': 'text/csv'
            }
        }
    )

    data = response.json()
    upload_url = data['uploadUrl']
    file_id = data['fileId']
    ```

    <Info>
      The `royaltySource` helps identify the file format. Common sources include: `spotify`, `apple`, `youtube`, `ditto`, `tunecore`, `distrokid`, `cdbaby`, etc.
    </Info>
  </Step>

  <Step title="Upload File to Cloud Storage">
    Upload your file to the provided secure URL.

    ```javascript Node.js theme={null}
    const fileBuffer = fs.readFileSync('spotify-royalties-jan-2024.csv');

    const uploadResponse = await fetch(uploadUrl, {
      method: 'PUT',
      headers: {
        'Content-Type': 'text/csv'
      },
      body: fileBuffer
    });

    if (uploadResponse.ok) {
      console.log('File uploaded successfully');
    }
    ```

    ```python Python theme={null}
    with open('spotify-royalties-jan-2024.csv', 'rb') as f:
        file_data = f.read()

    upload_response = requests.put(
        upload_url,
        headers={'Content-Type': 'text/csv'},
        data=file_data
    )

    if upload_response.status_code == 200:
        print('File uploaded successfully')
    ```
  </Step>

  <Step title="Confirm Upload">
    Notify the API that the upload is complete.

    ```javascript Node.js theme={null}
    const confirmResponse = await fetch(`https://api.royalti.io/file/${fileId}/confirm`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`
      }
    });

    const result = await confirmResponse.json();
    console.log('File status:', result.status);
    // Status: uploaded → processing → completed
    ```

    ```python Python theme={null}
    confirm_response = requests.post(
        f'https://api.royalti.io/file/{file_id}/confirm',
        headers=headers
    )

    result = confirm_response.json()
    print(f"File status: {result['status']}")
    ```
  </Step>

  <Step title="Monitor Processing Status">
    Check the status of your file processing.

    ```javascript Node.js theme={null}
    const statusResponse = await fetch(`https://api.royalti.io/file/${fileId}`, {
      headers: {
        'Authorization': `Bearer ${token}`
      }
    });

    const fileData = await statusResponse.json();
    console.log('Processing status:', fileData.status);
    console.log('Rows processed:', fileData.rowsProcessed);
    ```

    <Note>
      File processing happens asynchronously. Large files may take several minutes to complete.
    </Note>

    <Info>
      **Real-time updates available!** Instead of polling, connect via WebSocket to receive instant progress updates. See the [Real-Time Events Guide](/guides/real-time-events) for details.
    </Info>
  </Step>
</Steps>

## Real-Time Progress Monitoring

For a better user experience, use WebSocket events instead of polling for file status:

```javascript Node.js theme={null}
import { io } from 'socket.io-client';

// Connect with your JWT token
const socket = io('https://api.royalti.io', {
  auth: { token: token },
  path: '/socket.io'
});

// Listen for progress updates
socket.on('file:processing:progress', (data) => {
  console.log(`${data.fileName}: ${data.progress}% - ${data.message}`);
});

socket.on('file:processing:completed', (data) => {
  console.log(`Completed: ${data.fileName}`);
  // Fetch final file details or refresh dashboard
});

socket.on('file:processing:failed', (data) => {
  console.error(`Failed: ${data.error}`);
});
```

WebSocket events are user-scoped - you only receive events for files you uploaded. See the [Real-Time Events Guide](/guides/real-time-events) for complete setup and examples.

## Supported File Formats

### CSV and TSV Files

Upload comma-separated or tab-separated value files directly.

**Supported:**

* `.csv` - Comma-separated values
* `.tsv` - Tab-separated values
* `.txt` - Text files with delimiters

**Example:**

```javascript Node.js theme={null}
{
  fileMetaData: {
    fileName: 'royalties.csv',
    mimeType: 'text/csv'
  }
}
```

### Excel Files

Upload Excel workbooks in legacy or modern formats.

**Supported:**

* `.xls` - Excel 97-2003
* `.xlsx` - Excel 2007+

**Example:**

```javascript Node.js theme={null}
{
  fileMetaData: {
    fileName: 'royalties.xlsx',
    mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
  }
}
```

### ZIP Archives

Upload multiple files in a single ZIP archive for batch processing.

**Supported:**

* `.zip` - ZIP archive containing CSV/Excel files

**Example:**

```javascript Node.js theme={null}
{
  fileMetaData: {
    fileName: 'monthly-royalties.zip',
    mimeType: 'application/zip'
  }
}
```

<Warning>
  ZIP files are extracted and each file is processed individually. Ensure all files in the archive are for the same royalty source and period.
</Warning>

## Royalty Source Detection

### Automatic Recognition

The API can automatically detect file format based on headers and structure. You can provide the `royaltySource` parameter to improve accuracy:

```javascript Common Sources theme={null}
{
  royaltySource: 'spotify'     // Spotify for Artists
  royaltySource: 'apple'       // Apple Music
  royaltySource: 'youtube'     // YouTube Content ID
  royaltySource: 'fuga'        // FUGA
  royaltySource: 'ditto'       // Ditto Music
  royaltySource: 'tunecore'    // TuneCore
  royaltySource: 'distrokid'   // DistroKid
  royaltySource: 'cdbaby'      // CD Baby
  royaltySource: 'amuse'       // Amuse
  royaltySource: 'unitedmasters' // UnitedMasters
}
```

### Manual Source Specification

If the source isn't automatically detected, you can specify a custom source:

```javascript Node.js theme={null}
{
  royaltySource: 'custom-distributor',
  fileMetaData: {
    fileName: 'custom-royalties.csv',
    // ... other metadata
  }
}
```

## Managing Uploaded Files

### List All Files

Retrieve paginated list of uploaded royalty files:

```javascript Node.js theme={null}
const response = await fetch(
  'https://api.royalti.io/file?page=1&size=20&type=royalty',
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);

const { files, pagination } = await response.json();
```

**Query Parameters:**

* `page` - Page number (default: 1)
* `size` - Items per page (default: 20, max: 100)
* `type` - Filter by file type (`royalty`, `invoice`, `receipt`, `report`)
* `status` - Filter by status (`uploaded`, `processing`, `completed`, `failed`)
* `source` - Filter by royalty source

### Get File Details

Retrieve detailed information about a specific file:

```javascript Node.js theme={null}
const response = await fetch(`https://api.royalti.io/file/${fileId}`, {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

const fileData = await response.json();

console.log('File name:', fileData.originalname);
console.log('Status:', fileData.status);
console.log('Rows processed:', fileData.rowsProcessed);
console.log('Accounting period:', fileData.accountingPeriod);
console.log('Sales period:', fileData.salePeriod);
console.log('Download URL:', fileData.downloadUrl);
```

### Download Processed File

Get a download URL for the original or processed file:

```javascript Node.js theme={null}
const response = await fetch(`https://api.royalti.io/file/${fileId}/download`, {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

const { downloadUrl, expiresAt } = await response.json();

// Download the file
const fileResponse = await fetch(downloadUrl);
const fileBlob = await fileResponse.blob();
```

<Note>
  Download URLs are temporary and expire after 1 hour for security.
</Note>

### Delete File

Remove a royalty file and its associated data:

```javascript Node.js theme={null}
const response = await fetch(`https://api.royalti.io/file/${fileId}`, {
  method: 'DELETE',
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

const result = await response.json();
console.log(result.message); // "File deleted successfully"
```

<Warning>
  Deleting a file does not automatically recalculate accounting. If you've already processed earnings from this file, you'll need to manually adjust user balances.
</Warning>

## Royalty Analytics

### Summary Analytics

Get overall royalty statistics:

```javascript Node.js theme={null}
const response = await fetch(
  'https://api.royalti.io/royalty?startDate=2024-01-01&endDate=2024-12-31',
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);

const summary = await response.json();

console.log('Total royalty:', summary.Royalty);
console.log('Total streams:', summary.Streams);
console.log('Stream royalty:', summary.Streams_Royalty);
console.log('Total downloads:', summary.Downloads);
console.log('Download royalty:', summary.Downloads_Royalty);
console.log('Rate per 1000 plays:', summary.RatePer1K);
```

**Response Example:**

```json theme={null}
{
  "Royalty": 15234.50,
  "Streams": 1234567,
  "Streams_Royalty": 12000.00,
  "Downloads": 5432,
  "Downloads_Royalty": 3234.50,
  "Count": 1239999,
  "RatePer1K": 12.28,
  "RoyaltyPercentage": "+15.3%",
  "CountPercentage": "+22.1%",
  "PreviousRoyalty": 13234.22,
  "PreviousCount": 1015432
}
```

### By Platform (DSP)

Analyze royalties by streaming platform:

```javascript Node.js theme={null}
const response = await fetch(
  'https://api.royalti.io/royalty/by-dsp?startDate=2024-01-01&endDate=2024-12-31',
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);

const dspData = await response.json();

dspData.forEach(platform => {
  console.log(`${platform.DSP}: $${platform.Royalty} (${platform.Count} plays)`);
});
```

**Response Example:**

```json theme={null}
[
  {
    "DSP": "Spotify",
    "Royalty": 8500.00,
    "Count": 750000,
    "RatePer1K": 11.33,
    "RoyaltyPercentage": "+12.5%",
    "CountPercentage": "+18.2%"
  },
  {
    "DSP": "Apple Music",
    "Royalty": 4200.00,
    "Count": 325000,
    "RatePer1K": 12.92,
    "RoyaltyPercentage": "+8.3%",
    "CountPercentage": "+15.7%"
  }
]
```

### By Territory (Country)

Track royalties by geographic location:

```javascript Node.js theme={null}
const response = await fetch(
  'https://api.royalti.io/royalty/by-country?startDate=2024-01-01&endDate=2024-12-31&limit=10',
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);

const countryData = await response.json();
```

**Query Parameters:**

* `limit` - Number of top countries to return (default: 10)

### By Month

View month-over-month trends:

```javascript Node.js theme={null}
const response = await fetch(
  'https://api.royalti.io/royalty/by-month?startDate=2024-01-01&endDate=2024-12-31',
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);

const monthlyData = await response.json();

monthlyData.forEach(month => {
  console.log(`${month.Month}: $${month.Royalty} (${month.RoyaltyPercentage} change)`);
});
```

### By Artist

Analyze artist-specific performance:

```javascript Node.js theme={null}
const response = await fetch(
  `https://api.royalti.io/royalty/by-artist?startDate=2024-01-01&endDate=2024-12-31`,
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);

const artistData = await response.json();
```

### By Product/Asset

Track individual release or track performance:

```javascript Node.js theme={null}
const response = await fetch(
  `https://api.royalti.io/royalty/by-product/${productId}?startDate=2024-01-01&endDate=2024-12-31`,
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);

const productRoyalties = await response.json();
```

## Split Distribution

After royalty data is uploaded and processed, earnings are automatically distributed based on configured splits.

### How Distribution Works

1. **Royalty file uploaded** - Revenue data imported into the system
2. **Assets matched** - Tracks/products identified by ISRC, UPC, or name
3. **Splits applied** - Revenue shares calculated per split configuration
4. **User earnings updated** - Individual user balances increased

### Viewing User Earnings

Check how much each user has earned:

```javascript Node.js theme={null}
const response = await fetch(
  'https://api.royalti.io/accounting/getcurrentdue?sort=due&order=DESC',
  {
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);

const users = await response.json();

users.Users.forEach(user => {
  console.log(`${user.fullName}: $${user.Due} due`);
});
```

### Recalculating After Changes

If you modify splits or need to refresh calculations:

```javascript Node.js theme={null}
// Recalculate for specific user
const response = await fetch(
  `https://api.royalti.io/accounting/users/${userId}/recalculate?forceRefresh=true`,
  {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${token}`
    }
  }
);

const { jobId } = await response.json();
console.log('Recalculation queued:', jobId);
```

<Info>
  Large recalculations are processed in the background. Check the [Financial Data & Management Guide](/guides/financial-data-management) for details on monitoring progress.
</Info>

## Best Practices

### File Organization

1. **Use consistent naming** - Include source, period, and date in filenames
2. **One source per file** - Don't mix Spotify and Apple data in same file
3. **Specify periods accurately** - Use the actual accounting period from the statement

**Good naming examples:**

```
spotify-jan-2024-uploaded-2024-02-15.csv
apple-music-q4-2023.xlsx
youtube-december-2024.zip
```

### Data Validation

Before uploading, verify:

* ✅ File contains all required columns
* ✅ Dates are in correct format
* ✅ Currency values are numeric
* ✅ ISRCs/UPCs match your catalog
* ✅ File encoding is UTF-8 (for special characters)

### Error Handling

Always check upload status and handle errors:

```javascript Node.js theme={null}
async function uploadRoyaltyFile(fileBuffer, metadata) {
  try {
    // Request upload URL
    const urlResponse = await fetch('https://api.royalti.io/file/upload-url', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${token}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(metadata)
    });

    if (!urlResponse.ok) {
      const error = await urlResponse.json();
      throw new Error(`URL request failed: ${error.message}`);
    }

    const { uploadUrl, fileId } = await urlResponse.json();

    // Upload file
    const uploadResponse = await fetch(uploadUrl, {
      method: 'PUT',
      body: fileBuffer
    });

    if (!uploadResponse.ok) {
      throw new Error('File upload failed');
    }

    // Confirm upload
    const confirmResponse = await fetch(
      `https://api.royalti.io/file/${fileId}/confirm`,
      {
        method: 'POST',
        headers: { 'Authorization': `Bearer ${token}` }
      }
    );

    const result = await confirmResponse.json();
    return { fileId, status: result.status };

  } catch (error) {
    console.error('Upload failed:', error.message);
    throw error;
  }
}
```

### Processing Large Files

For files with 100,000+ rows:

1. **Use ZIP compression** - Reduces upload time
2. **Monitor status regularly** - Check every 30-60 seconds
3. **Be patient** - Large files may take 10-30 minutes
4. **Verify completion** - Don't assume success, check final status

## Troubleshooting

<AccordionGroup>
  <Accordion title="File stuck in 'processing' status">
    **Possible causes:**

    * File is very large (processing can take 10-30 minutes)
    * Invalid data format (system attempting to parse)
    * Network issues during upload

    **Solutions:**

    1. Wait 30 minutes before taking action
    2. Check file status endpoint for error details
    3. If still stuck after 1 hour, contact support with `fileId`
  </Accordion>

  <Accordion title="File status shows 'failed'">
    **Common reasons:**

    * Unrecognized file format
    * Missing required columns
    * Invalid data types (e.g., text in numeric columns)
    * File encoding issues

    **Solutions:**

    1. Check file details for specific error message
    2. Verify file format matches royalty source
    3. Ensure file encoding is UTF-8
    4. Try re-uploading with `royaltySource` specified
  </Accordion>

  <Accordion title="No earnings calculated after upload">
    **Checklist:**

    * File status is 'completed'
    * Assets/products exist in catalog with matching ISRCs/UPCs
    * Splits are configured for the assets
    * Accounting period matches file period

    **Solutions:**

    1. Verify assets exist: `GET /asset?isrc={ISRC}`
    2. Check split configuration: `GET /split?asset={assetId}`
    3. Force accounting recalculation
    4. Review royalty data: `GET /royalty/by-asset/{assetId}`
  </Accordion>

  <Accordion title="Upload URL expired">
    **Cause:** Upload URLs expire after 1 hour for security.

    **Solution:** Request a new upload URL and upload immediately.
  </Accordion>

  <Accordion title="Duplicate data detected">
    **Prevention:**

    * Track which files you've uploaded
    * Use consistent naming conventions
    * Check uploaded files list before uploading

    **If duplicates exist:**

    1. Delete the duplicate file
    2. Recalculate accounting for affected users
  </Accordion>
</AccordionGroup>

## API Reference

For complete endpoint documentation, see:

**File Upload:**

* [POST /file/upload-url](/api-reference/file/post-file-upload-url) - Request upload URL
* [POST /file/confirm-upload-completion](/api-reference/file/post-file-confirm-upload-completion) - Confirm upload
* [GET /file](/api-reference/file/get-file) - List files
* [GET /file/{id}](/api-reference/file/get-file-id) - Get file details
* [DELETE /file/royalty/{id}](/api-reference/file/delete-file-royalty-id) - Delete file

**Analytics:**

* [GET /royalty](/api-reference/royalties/get-royalty) - Summary analytics
* [GET /royalty/dsp](/api-reference/royalties/get-royalty-dsp) - Platform breakdown
* [GET /royalty/country](/api-reference/royalties/get-royalty-country) - Territory breakdown
* [GET /royalty/month](/api-reference/royalties/get-royalty-month) - Monthly trends
* [GET /royalty/artist](/api-reference/royalties/get-royalty-artist) - Artist performance

**Related Guides:**

* [Splits Management](/guides/splits-management) - Configure revenue distribution
* [Financial Data & Management](/guides/financial-data-management) - View earnings
* [User Management](/guides/user-management) - Manage collaborators
