Overview
Learn practical workflows for common tasks like onboarding collaborators, managing split changes, processing royalties, and handling payments. These examples show how different API endpoints work together to accomplish real-world goals.
What You’ll Learn
Onboarding collaborators - Create users, assign splits, verify earnings
Managing split changes - Update revenue shares and recalculate accounting
Processing royalties - Upload files, check status, distribute earnings
Payment workflows - Generate reports, create payments, update balances
Territory-specific deals - Handle different splits by region
Collaborative projects - Manage multi-contributor revenue sharing
Workflow 1: Onboarding a New Collaborator
Add a new user to your workspace and configure their revenue share.
Create User Account
Create a user with invitation email. const userResponse = await fetch ( 'https://api.royalti.io/user/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
firstName: 'Sarah' ,
lastName: 'Producer' ,
nickName: 'sarahpro' ,
email: 'sarah@example.com' ,
role: 'user' ,
userType: [ 'producer' , 'artist' ],
sendEmail: true ,
message: 'Welcome to our label workspace!'
})
});
const userData = await userResponse . json ();
const userId = userData . id ;
console . log ( 'User created:' , userId );
user_response = requests.post(
'https://api.royalti.io/user/' ,
headers = headers,
json = {
'firstName' : 'Sarah' ,
'lastName' : 'Producer' ,
'nickName' : 'sarahpro' ,
'email' : 'sarah@example.com' ,
'role' : 'user' ,
'userType' : [ 'producer' , 'artist' ],
'sendEmail' : True ,
'message' : 'Welcome to our label workspace!'
}
)
user_data = user_response.json()
user_id = user_data[ 'id' ]
print ( f 'User created: { user_id } ' )
Assign to Existing Splits
Add the user to existing asset splits. const splitResponse = await fetch (
`https://api.royalti.io/split/ ${ existingSplitId } ` ,
{
method: 'PUT' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
split: [
{
TenantUserId: userId ,
split: 25.0 // 25% share
},
{
TenantUserId: labelUserId ,
split: 75.0 // Remaining 75%
}
]
})
}
);
console . log ( 'Split updated successfully' );
Create Future Release Splits
Set up default splits for upcoming projects. const newSplitResponse = await fetch ( 'https://api.royalti.io/split/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
AssetId: newAssetId ,
split: [
{
TenantUserId: userId ,
split: 30.0
},
{
TenantUserId: labelUserId ,
split: 70.0
}
],
startDate: new Date (). toISOString (),
endDate: null // Open-ended
})
});
console . log ( 'Future split configured' );
Verify Earnings Calculation
Check that earnings are calculated correctly for the user. const statsResponse = await fetch (
`https://api.royalti.io/accounting/ ${ userId } /stats?forceRefresh=true` ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const earnings = await statsResponse . json ();
console . log ( 'User Earnings:' , {
gross: earnings . Royalty_Share ,
paid: earnings . paid ,
due: earnings . due
});
Set Payment Preferences (Optional)
Configure payment settings for the user. await fetch ( 'https://api.royalti.io/payment-setting/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
userId: userId ,
minimumPayment: 50.00 ,
currency: 'USD' ,
paymentMethod: 'bank_transfer' ,
paymentDetails: {
bankName: 'Chase Bank' ,
accountHolderName: 'Sarah Producer'
}
})
});
console . log ( 'Payment settings configured' );
Workflow 2: Managing Revenue Distribution Changes
Handle changes to split agreements while maintaining historical accuracy.
Scenario: Split Terms Changing Mid-Year
When split agreements change, choose the appropriate update method:
Option A: Update Existing Split (Simple Change)
For simple percentage adjustments without time-based conditions:
// Update split percentages (applies to all historical data)
const response = await fetch ( `https://api.royalti.io/split/ ${ splitId } ` , {
method: 'PUT' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
split: [
{
TenantUserId: artistId ,
split: 65.0 // Changed from 60%
},
{
TenantUserId: labelId ,
split: 35.0 // Changed from 40%
}
]
})
});
console . log ( 'Split updated' );
Updating an existing split applies the new percentages to ALL historical data. Use temporal splits (Option B) for changes that should only apply going forward.
Option B: Create Temporal Split (Time-Based Change)
For changes that apply from a specific date forward:
// Create new split effective from specific date
const newSplitResponse = await fetch ( 'https://api.royalti.io/split/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
AssetId: assetId ,
split: [
{
TenantUserId: artistId ,
split: 70.0 // New terms starting July 1
},
{
TenantUserId: labelId ,
split: 30.0
}
],
startDate: '2025-07-01' , // Effective date
endDate: null // Open-ended
})
});
console . log ( 'Temporal split created' );
# Create temporal split
new_split_response = requests.post(
'https://api.royalti.io/split/' ,
headers = headers,
json = {
'AssetId' : asset_id,
'split' : [
{ 'TenantUserId' : artist_id, 'split' : 70.0 },
{ 'TenantUserId' : label_id, 'split' : 30.0 }
],
'startDate' : '2025-07-01' ,
'endDate' : None
}
)
print ( 'Temporal split created' )
Refresh Earnings After Changes
After making split changes, refresh accounting data:
// Request recalculation with latest split data
const refreshResponse = await fetch (
`https://api.royalti.io/accounting/users/ ${ userId } /recalculate?forceRefresh=true` ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const { jobId } = await refreshResponse . json ();
// Check updated earnings
const statsResponse = await fetch (
`https://api.royalti.io/accounting/ ${ userId } /stats` ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const earnings = await statsResponse . json ();
console . log ( 'Updated earnings:' , earnings . due );
Notify Affected Users
After split changes, notify collaborators:
const affectedUserIds = [ artistId , producerId , labelId ];
for ( const userId of affectedUserIds ) {
await fetch ( 'https://api.royalti.io/notifications/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
recipientIds: [ userId ],
type: 'split_updated' ,
message: 'Your revenue split terms have been updated' ,
metadata: {
assetId: assetId ,
effectiveDate: '2025-07-01'
}
})
});
}
console . log ( 'Notifications sent' );
Workflow 3: Processing and Distributing Royalties
Complete workflow from file upload to user earnings distribution.
Upload Royalty File
Upload CSV or ZIP file from streaming platform. // Step 1: 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 ({
royaltySource: 'spotify' ,
accountingPeriod: '2024-03-01' ,
salePeriod: '2024-03-01' ,
fileMetaData: {
fileName: 'spotify-march-2024.csv' ,
fileSize: 1024000 ,
mimeType: 'text/csv'
}
})
});
const { uploadUrl , fileId } = await urlResponse . json ();
// Step 2: Upload file
const fileBuffer = fs . readFileSync ( 'spotify-march-2024.csv' );
await fetch ( uploadUrl , {
method: 'PUT' ,
body: fileBuffer
});
// Step 3: Confirm upload
await fetch ( `https://api.royalti.io/file/ ${ fileId } /confirm` , {
method: 'POST' ,
headers: { 'Authorization' : `Bearer ${ token } ` }
});
console . log ( 'File uploaded:' , fileId );
The system automatically recognizes the file format and reporting period from the file contents and metadata.
Monitor Processing
Check file processing status. const statusResponse = await fetch ( `https://api.royalti.io/file/ ${ fileId } ` , {
headers: { 'Authorization' : `Bearer ${ token } ` }
});
const fileStatus = await statusResponse . json ();
console . log ( 'Status:' , fileStatus . status );
console . log ( 'Rows processed:' , fileStatus . rowsProcessed );
// Status: uploaded → processing → completed
Large files may take several minutes to process. Poll the status endpoint every 30-60 seconds.
Verify Earnings Updated
Check that user balances were updated. // Get current balances for all users
const dueResponse = await fetch (
'https://api.royalti.io/accounting/getcurrentdue?sort=due&order=DESC' ,
{
headers: { 'Authorization' : `Bearer ${ token } ` }
}
);
const { Users } = await dueResponse . json ();
// Filter users meeting payment threshold
const eligible = Users . filter ( user => user . Due >= 50 );
console . log ( ` ${ eligible . length } users eligible for payment` );
Review Updated Earnings
Check detailed earnings for specific users. const statsResponse = await fetch (
`https://api.royalti.io/accounting/ ${ userId } /stats?include=royalty` ,
{
headers: { 'Authorization' : `Bearer ${ token } ` }
}
);
const stats = await statsResponse . json ();
console . log ( 'Gross earnings:' , stats . Royalty_Share );
console . log ( 'Total streams:' , stats . royaltySummary . Streams );
console . log ( 'Stream royalty:' , stats . royaltySummary . Streams_Royalty );
console . log ( 'Amount due:' , stats . due );
Workflow 4: Payment Cycle Management
Monthly payment workflow from eligibility check to payment execution.
Generate Current Due Report
Get all users with outstanding balances. const reportResponse = await fetch (
'https://api.royalti.io/accounting/getcurrentdue?size=100' ,
{
headers: { 'Authorization' : `Bearer ${ token } ` }
}
);
const { Users , totalUsers } = await reportResponse . json ();
console . log ( `Total users with balances: ${ totalUsers } ` );
report_response = requests.get(
'https://api.royalti.io/accounting/getcurrentdue?size=100' ,
headers = headers
)
data = report_response.json()
users = data[ 'Users' ]
total_users = data[ 'totalUsers' ]
print ( f 'Total users with balances: { total_users } ' )
Filter by Payment Settings
Filter users who meet minimum threshold and have payment settings. const eligibleUsers = Users . filter ( user => {
const hasMinimum = user . Due >= ( user . paymentSettings ?. minimumPayment || 50 );
const hasSettings = user . paymentSettings !== null ;
const notRequested = ! user . paymentRequested ;
return hasMinimum && hasSettings && notRequested ;
});
console . log ( ` ${ eligibleUsers . length } users eligible for payment` );
Create Payment Batch
Create payment records for eligible users. const payments = await Promise . all (
eligibleUsers . map ( user =>
fetch ( 'https://api.royalti.io/payment/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
user: user . TenantUserId ,
title: 'March 2024 Royalty Payment' ,
transactionDate: new Date (). toISOString (),
currency: 'USD' ,
amount: user . Due ,
amountUSD: user . Due ,
conversionRate: 1.0 ,
memo: 'Monthly royalty distribution'
})
})
)
);
console . log ( ` ${ payments . length } payment records created` );
Process External Payments
After processing payments through your payment provider, update records. for ( const paymentResponse of payments ) {
const payment = await paymentResponse . json ();
// Process through Stripe, PayPal, etc.
// const externalPayment = await stripeClient.transfers.create({...});
// Update payment record with external transaction ID
await fetch ( `https://api.royalti.io/payment/ ${ payment . data . id } ` , {
method: 'PUT' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
memo: `Paid via Stripe: ${ externalTransactionId } `
})
});
}
console . log ( 'Payments processed and recorded' );
Notify Users
Send payment confirmation notifications. await fetch ( 'https://api.royalti.io/notifications/bulk' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
recipientIds: eligibleUsers . map ( u => u . id ),
type: 'payment_completed' ,
message: 'Your royalty payment has been processed'
})
});
console . log ( 'Payment notifications sent' );
Common Use Cases
Use Case 1: Label with Multiple Artists
Set up a label workspace with default splits for all artists.
// Step 1: Configure label-level defaults (70% artist, 30% label)
await fetch ( 'https://api.royalti.io/default-split/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
TenantId: tenantId ,
split: [
{ role: 'artist' , split: 70.0 },
{ role: 'label' , split: 30.0 }
]
})
});
// Step 2: Add artists (they'll inherit default splits)
const artistResponse = await fetch ( 'https://api.royalti.io/user/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
firstName: 'Artist' ,
lastName: 'Name' ,
email: 'artist@example.com' ,
userType: 'artist' ,
role: 'user'
})
});
// Step 3: Upload catalog → defaults apply automatically
// Step 4: Process royalties → accounting calculated per artist
Use Case 2: Collaborative Project
Manage splits for a multi-contributor project.
// Step 1: Create project-specific users
const usersResponse = await fetch ( 'https://api.royalti.io/user/bulk' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
users: [
{ firstName: 'Producer' , lastName: 'One' , email: 'producer@example.com' , userType: 'producer' },
{ firstName: 'Engineer' , lastName: 'Two' , email: 'engineer@example.com' , userType: 'engineer' },
{ firstName: 'Artist' , lastName: 'Three' , email: 'artist@example.com' , userType: 'artist' }
],
sendEmail: true
})
});
const users = await usersResponse . json ();
const [ producer , engineer , artist ] = users . data ;
// Step 2: Assign custom splits for this project
await fetch ( 'https://api.royalti.io/split/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
ProductId: projectProductId ,
split: [
{ TenantUserId: producer . id , split: 40.0 },
{ TenantUserId: engineer . id , split: 10.0 },
{ TenantUserId: artist . id , split: 50.0 }
]
})
});
// Step 3: All royalties automatically distributed per split
// Step 4: Each user sees their share in accounting
Use Case 3: Territory-Specific Deals
Handle different split agreements by territory.
// Step 1: Create default worldwide split
await fetch ( 'https://api.royalti.io/split/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
AssetId: assetId ,
split: [
{ TenantUserId: artistId , split: 70.0 },
{ TenantUserId: labelId , split: 30.0 }
]
})
});
// Step 2: Create territory-specific override for Japan
await fetch ( 'https://api.royalti.io/split/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
AssetId: assetId ,
split: [
{ TenantUserId: artistId , split: 50.0 },
{ TenantUserId: labelId , split: 30.0 },
{ TenantUserId: distributorId , split: 20.0 }
],
territory: 'JP' , // Only applies to Japan
startDate: '2025-01-01'
})
});
// The system automatically applies the correct split based on territory
Integration Sequences
Sequence 1: User → Splits → Accounting
Complete flow from user creation to earnings calculation:
// Step 1: Create user
const userResponse = await fetch ( 'https://api.royalti.io/user/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
firstName: 'John' ,
lastName: 'Doe' ,
email: 'john@example.com'
})
});
const { id : userId } = await userResponse . json ();
// Step 2: Assign to splits
await fetch ( 'https://api.royalti.io/split/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
AssetId: assetId ,
split: [
{ TenantUserId: userId , split: 50.0 },
{ TenantUserId: labelId , split: 50.0 }
]
})
});
// Step 3: Process royalties (file upload - see Workflow 3)
// Step 4: View earnings
const earningsResponse = await fetch (
`https://api.royalti.io/accounting/ ${ userId } /stats` ,
{
headers: { 'Authorization' : `Bearer ${ token } ` }
}
);
const earnings = await earningsResponse . json ();
console . log ( 'User earnings:' , earnings . due );
Sequence 2: Splits → Accounting Update
Update splits and verify accounting recalculation:
// Step 1: Update split
await fetch ( `https://api.royalti.io/split/ ${ splitId } ` , {
method: 'PUT' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
split: [
{ TenantUserId: userId , split: 60.0 }, // Increased from 50%
{ TenantUserId: labelId , split: 40.0 }
]
})
});
// Step 2: Request fresh calculation
const statsResponse = await fetch (
`https://api.royalti.io/accounting/ ${ userId } /stats?forceRefresh=true` ,
{
headers: { 'Authorization' : `Bearer ${ token } ` }
}
);
const updatedStats = await statsResponse . json ();
console . log ( 'Updated earnings:' , updatedStats . due );
Sequence 3: Payment → Balance Update
Create payment and verify balance adjustment:
// Step 1: Create payment
const paymentResponse = await fetch ( 'https://api.royalti.io/payment/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
user: userId ,
title: 'Monthly Payment' ,
transactionDate: new Date (). toISOString (),
currency: 'USD' ,
amount: 500.00 ,
amountUSD: 500.00 ,
conversionRate: 1.0
})
});
// Step 2: Check updated balance
const statsResponse = await fetch (
`https://api.royalti.io/accounting/ ${ userId } /stats` ,
{
headers: { 'Authorization' : `Bearer ${ token } ` }
}
);
const stats = await statsResponse . json ();
console . log ( 'Paid amount increased:' , stats . paid );
console . log ( 'Due amount decreased:' , stats . due );
Best Practices
Data Consistency
Create users before splits - Users must exist before being assigned to splits
Validate splits before activation - Ensure splits add up to 100% and have valid date ranges
Use temporal splits for term changes - Preserves historical accuracy
Verify data after changes - Always check results after updates
Workflow Efficiency
Batch user creation - Use bulk endpoints for multiple users
Set default splits - Apply workspace-level defaults for consistency
Monitor file processing - Poll status endpoints for upload completion
Check eligibility first - Filter users before creating payments
Communication
Notify users of changes - Keep collaborators informed via notifications
Document split terms - Store agreements in metadata fields
Regular reporting - Send periodic earning statements
Payment confirmations - Notify users when payments are processed
Troubleshooting
Earnings not updating after split change
Solution:
Verify split was saved successfully (check API response)
Request fresh calculation: GET /accounting/{id}/stats?forceRefresh=true
Verify split date ranges include current period
Check that royalty data exists for the asset
User missing from accounting
Checklist:
User has at least one active split
Split date ranges include current period
Asset/Product has royalty data uploaded
Split percentages are valid (0-100)
Verify: // Check user splits
const splits = await fetch ( `https://api.royalti.io/split?userId= ${ userId } ` , {
headers: { 'Authorization' : `Bearer ${ token } ` }
});
// Check user stats
const stats = await fetch ( `https://api.royalti.io/accounting/ ${ userId } /stats` , {
headers: { 'Authorization' : `Bearer ${ token } ` }
});
Payment not deducted from due amount
Solution:
Verify payment status is successfully created
Check payment amount matches expected value
Request fresh accounting data: GET /accounting/{id}/stats?forceRefresh=true
Review transaction history to confirm payment was recorded
Split validation error: total exceeds 100%
Solution:
Review all split shares for the asset
Ensure percentages add up to exactly 100%
Check for overlapping temporal splits
Update split shares before saving:
// Validate splits add to 100%
const total = splits . reduce (( sum , s ) => sum + s . split , 0 );
if ( Math . abs ( total - 100 ) > 0.01 ) {
console . error ( `Splits total ${ total } %, must equal 100%` );
}
File upload stuck in processing
Check status: const status = await fetch ( `https://api.royalti.io/file/ ${ fileId } ` , {
headers: { 'Authorization' : `Bearer ${ token } ` }
});
const fileData = await status . json ();
console . log ( 'Status:' , fileData . status );
console . log ( 'Error:' , fileData . error );
Common causes:
Large file (may take 10-30 minutes)
Invalid file format
Missing required columns
File encoding issues
Solutions:
Wait 30 minutes for large files
Check error details in file status
Verify file format matches source
Re-upload with correct royaltySource specified
API Reference
Related Guides:
Key Endpoints:
Summary
These workflows demonstrate how to combine user management, splits, accounting, and payments for common scenarios:
Onboarding - Create users, assign splits, verify earnings
Split changes - Update terms, recalculate accounting, notify users
Royalty processing - Upload files, monitor progress, distribute earnings
Payments - Generate reports, create payments, update balances
Follow these patterns to build efficient royalty management workflows that scale with your business.