Skip to main content

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

All royalty endpoints require authentication with a Bearer token.
Node.js
const token = 'your_jwt_token';
const headers = {
  'Authorization': `Bearer ${token}`,
  'Content-Type': 'application/json'
};
Python
import requests

token = 'your_jwt_token'
headers = {
    'Authorization': f'Bearer {token}',
    'Content-Type': 'application/json'
}

Quick Start: Upload Your First Royalty File

1

Request Upload URL

Get a secure upload URL for your royalty file.
Node.js
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
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']
The royaltySource helps identify the file format. Common sources include: spotify, apple, youtube, ditto, tunecore, distrokid, cdbaby, etc.
2

Upload File to Cloud Storage

Upload your file to the provided secure URL.
Node.js
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
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')
3

Confirm Upload

Notify the API that the upload is complete.
Node.js
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
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']}")
4

Monitor Processing Status

Check the status of your file processing.
Node.js
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);
File processing happens asynchronously. Large files may take several minutes to complete.
Real-time updates available! Instead of polling, connect via WebSocket to receive instant progress updates. See the Real-Time Events Guide for details.

Real-Time Progress Monitoring

For a better user experience, use WebSocket events instead of polling for file status:
Node.js
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 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:
Node.js
{
  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:
Node.js
{
  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:
Node.js
{
  fileMetaData: {
    fileName: 'monthly-royalties.zip',
    mimeType: 'application/zip'
  }
}
ZIP files are extracted and each file is processed individually. Ensure all files in the archive are for the same royalty source and period.

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:
Common Sources
{
  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:
Node.js
{
  royaltySource: 'custom-distributor',
  fileMetaData: {
    fileName: 'custom-royalties.csv',
    // ... other metadata
  }
}

Managing Uploaded Files

List All Files

Retrieve paginated list of uploaded royalty files:
Node.js
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:
Node.js
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:
Node.js
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();
Download URLs are temporary and expire after 1 hour for security.

Delete File

Remove a royalty file and its associated data:
Node.js
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"
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.

Royalty Analytics

Summary Analytics

Get overall royalty statistics:
Node.js
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:
{
  "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:
Node.js
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:
[
  {
    "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:
Node.js
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:
Node.js
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:
Node.js
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:
Node.js
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:
Node.js
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:
Node.js
// 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);
Large recalculations are processed in the background. Check the Financial Data & Management Guide for details on monitoring progress.

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:
Node.js
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

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
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
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}
Cause: Upload URLs expire after 1 hour for security.Solution: Request a new upload URL and upload immediately.
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

API Reference

For complete endpoint documentation, see: File Upload: Analytics: Related Guides: