Overview
Access comprehensive financial reporting and management tools through the Royalti.io API. View real-time earnings calculations, track expenses and revenue, manage payments, and maintain accurate accounting records across your music catalog.
Key Features
Real-time earnings - View current earnings and payment status for all users
Automatic calculations - Earnings updated automatically when royalties upload or splits change
Expense tracking - Record costs at any level (user, artist, product, asset, general)
Revenue management - Track additional income sources beyond streaming royalties
Transaction history - Complete audit trail of all financial activity
Flexible reporting - Monthly breakdowns, summaries, and custom date ranges
Multi-currency support - Handle international transactions with automatic USD conversion
Prerequisites
Required Data
Before accessing financial features, ensure you have:
User accounts - Collaborators with earnings
Configured splits - Revenue distribution defined with percentages
Royalty data uploaded - Revenue data processed from DSPs/distributors
Payment settings - Bank accounts or payment methods configured (for payments)
Authentication
All financial endpoints require authentication with a Bearer token.
const headers = {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
};
import requests
headers = {
'Authorization' : f 'Bearer { token } ' ,
'Content-Type' : 'application/json'
}
Quick Start: Complete Financial Cycle
Check User Earnings
View earnings breakdown for a specific user: const response = await fetch (
`https://api.royalti.io/accounting/ ${ userId } /stats` ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const stats = await response . json ();
console . log ( 'Gross earnings:' , stats . Royalty_Share );
console . log ( 'Amount paid:' , stats . paid );
console . log ( 'Amount due:' , stats . due );
response = requests.get(
f 'https://api.royalti.io/accounting/ { user_id } /stats' ,
headers = headers
)
stats = response.json()
print ( f "Gross earnings: $ { stats[ 'Royalty_Share' ] } " )
print ( f "Amount paid: $ { stats[ 'paid' ] } " )
print ( f "Amount due: $ { stats[ 'due' ] } " )
Track an Expense
Record costs associated with a product release: const response = await fetch ( 'https://api.royalti.io/expense/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
title: 'Marketing Campaign' ,
type: 'product' ,
id: 'product-uuid' ,
transactionDate: '2024-03-15' ,
currency: 'USD' ,
amount: 2500.00 ,
amountUSD: 2500.00 ,
memo: 'Social media advertising for album release'
})
});
const expense = await response . json ();
response = requests.post(
'https://api.royalti.io/expense/' ,
headers = headers,
json = {
'title' : 'Marketing Campaign' ,
'type' : 'product' ,
'id' : 'product-uuid' ,
'transactionDate' : '2024-03-15' ,
'currency' : 'USD' ,
'amount' : 2500.00 ,
'amountUSD' : 2500.00 ,
'memo' : 'Social media advertising for album release'
}
)
expense = response.json()
Record Additional Revenue
Track income beyond streaming royalties: const response = await fetch ( 'https://api.royalti.io/revenue/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
title: 'Sync Licensing Deal' ,
amount: 10000.00 ,
currency: 'USD' ,
amountUSD: 10000.00 ,
transactionDate: '2024-03-20T10:00:00Z' ,
ArtistId: 'artist-uuid' ,
memo: 'TV commercial licensing'
})
});
const revenue = await response . json ();
response = requests.post(
'https://api.royalti.io/revenue/' ,
headers = headers,
json = {
'title' : 'Sync Licensing Deal' ,
'amount' : 10000.00 ,
'currency' : 'USD' ,
'amountUSD' : 10000.00 ,
'transactionDate' : '2024-03-20T10:00:00Z' ,
'ArtistId' : 'artist-uuid' ,
'memo' : 'TV commercial licensing'
}
)
revenue = response.json()
Review Updated Balances
Verify accounting reflects all transactions: const response = await fetch (
'https://api.royalti.io/accounting/getcurrentdue?sort=due&order=DESC' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const { Users } = await response . json ();
Users . forEach ( user => {
console . log ( ` ${ user . fullName } : $ ${ user . Due } due` );
});
response = requests.get(
'https://api.royalti.io/accounting/getcurrentdue?sort=due&order=DESC' ,
headers = headers
)
data = response.json()
users = data[ 'Users' ]
for user in users:
print ( f " { user[ 'fullName' ] } : $ { user[ 'Due' ] } due" )
Viewing Earnings & Accounting
Get User Accounting Statistics
Retrieve comprehensive earnings data for a specific user:
const response = await fetch (
`https://api.royalti.io/accounting/ ${ userId } /stats` ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const stats = await response . json ();
response = requests.get(
f 'https://api.royalti.io/accounting/ { user_id } /stats' ,
headers = headers
)
stats = response.json()
Response Fields:
Royalty_Share - Gross earnings from all splits
paid - Total amount paid to user
due - Outstanding balance (Royalty_Share - paid)
Query Parameters:
include=royalty - Include detailed royalty breakdown
forceRefresh=true - Recalculate from source data
Example Response:
{
"Royalty_Share" : 1135.28 ,
"paid" : 100.00 ,
"due" : 1035.28
}
Get Detailed Royalty Breakdown
Include royalty summary for streams, downloads, and rates:
const response = await fetch (
`https://api.royalti.io/accounting/ ${ userId } /stats?include=royalty` ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const stats = await response . json ();
console . log ( 'Total streams:' , stats . royaltySummary . Streams );
console . log ( 'Stream royalty:' , stats . royaltySummary . Streams_Royalty );
console . log ( 'Total downloads:' , stats . royaltySummary . Downloads );
console . log ( 'Download royalty:' , stats . royaltySummary . Downloads_Royalty );
console . log ( 'Rate per 1K:' , stats . royaltySummary . RatePer1K );
response = requests.get(
f 'https://api.royalti.io/accounting/ { user_id } /stats?include=royalty' ,
headers = headers
)
stats = response.json()
if 'royaltySummary' in stats:
summary = stats[ 'royaltySummary' ]
print ( f "Total streams: { summary[ 'Streams' ] } " )
print ( f "Stream royalty: $ { summary[ 'Streams_Royalty' ] } " )
print ( f "Total downloads: { summary[ 'Downloads' ] } " )
print ( f "Download royalty: $ { summary[ 'Downloads_Royalty' ] } " )
Current Balances for All Users
Get list of all users with their outstanding balances:
const response = await fetch (
'https://api.royalti.io/accounting/getcurrentdue?sort=due&order=DESC&page=1&size=20' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const { Users , totalUsers , page , totalPages } = await response . json ();
Users . forEach ( user => {
console . log ( ` ${ user . fullName } : $ ${ user . Due } due` );
});
response = requests.get(
'https://api.royalti.io/accounting/getcurrentdue?sort=due&order=DESC' ,
headers = headers
)
data = response.json()
for user in data[ 'Users' ]:
print ( f " { user[ 'fullName' ] } : $ { user[ 'Due' ] } due" )
Query Parameters:
q - Search by user name
page - Page number (default: 1)
size - Page size (default: 10, max: 100)
sort - Sort field: gross, net, paid, due (default: due)
order - Sort order: ASC or DESC (default: DESC)
Response Fields per User:
{
"id" : "user-id" ,
"firstName" : "John" ,
"lastName" : "Doe" ,
"fullName" : "John Doe" ,
"email" : "[email protected] " ,
"TenantUserId" : "tenant-user-id" ,
"Gross" : 2500.75 ,
"Net" : 2250.68 ,
"Paid" : 1500.00 ,
"Due" : 750.68 ,
"paymentRequested" : false ,
"paymentRequestId" : null
}
Total Outstanding for Workspace
Get aggregate due amount across all users:
const response = await fetch (
'https://api.royalti.io/accounting/gettotaldue' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const total = await response . json ();
console . log ( 'Total gross:' , total . totalGross );
console . log ( 'Total paid:' , total . totalPaid );
console . log ( 'Total due:' , total . totalDue );
console . log ( 'User count:' , total . userCount );
response = requests.get(
'https://api.royalti.io/accounting/gettotaldue' ,
headers = headers
)
total = response.json()
print ( f "Total gross: $ { total[ 'totalGross' ] } " )
print ( f "Total paid: $ { total[ 'totalPaid' ] } " )
print ( f "Total due: $ { total[ 'totalDue' ] } " )
print ( f "User count: { total[ 'userCount' ] } " )
Response Example:
{
"message" : "success" ,
"totalGross" : 150000.50 ,
"totalPaid" : 100000.00 ,
"totalDue" : 50000.50 ,
"userCount" : 125
}
Transaction History
Get paginated list of all financial transactions:
const response = await fetch (
'https://api.royalti.io/accounting/transactions?page=1&size=20' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const { transaction , currentPage , totalPages , totalItem } = await response . json ();
transaction . forEach ( txn => {
console . log ( ` ${ txn . type } : $ ${ txn . amount } - ${ txn . TenantUser . fullName } ` );
});
response = requests.get(
'https://api.royalti.io/accounting/transactions?page=1&size=20' ,
headers = headers
)
data = response.json()
transactions = data[ 'transaction' ]
for txn in transactions:
print ( f " { txn[ 'type' ] } : $ { txn[ 'amount' ] } - { txn[ 'TenantUser' ][ 'fullName' ] } " )
Query Parameters:
page - Page number (default: 1)
size - Items per page (default: 10, max: 100)
start - Start date filter (ISO format: YYYY-MM-DD)
end - End date filter (ISO format: YYYY-MM-DD)
Transaction Types:
payment - Payment made to user
revenue - Revenue received
expense - Expense recorded
paymentRequest - Payment request created
Filter by Date Range:
const response = await fetch (
'https://api.royalti.io/accounting/transactions?start=2024-01-01&end=2024-12-31' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
response = requests.get(
'https://api.royalti.io/accounting/transactions?start=2024-01-01&end=2024-12-31' ,
headers = headers
)
data = response.json()
Transaction Summary
Get aggregated totals across all transaction types:
const response = await fetch (
'https://api.royalti.io/accounting/transactions/summary' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const { summary } = await response . json ();
console . log ( 'Total payments:' , summary . totalPayments );
console . log ( 'Total revenues:' , summary . totalRevenues );
console . log ( 'Total expenses:' , summary . totalExpenses );
console . log ( 'Net amount:' , summary . netAmount );
response = requests.get(
'https://api.royalti.io/accounting/transactions/summary' ,
headers = headers
)
data = response.json()
summary = data[ 'summary' ]
print ( f "Total payments: $ { summary[ 'totalPayments' ] } " )
print ( f "Total revenues: $ { summary[ 'totalRevenues' ] } " )
print ( f "Total expenses: $ { summary[ 'totalExpenses' ] } " )
print ( f "Net amount: $ { summary[ 'netAmount' ] } " )
Response Example:
{
"message" : "success" ,
"summary" : {
"totalPayments" : 50000.00 ,
"totalRevenues" : 125000.50 ,
"totalExpenses" : 15000.25 ,
"netAmount" : 60000.25
}
}
The summary provides all-time totals for the workspace. Use the monthly breakdown endpoint for time-series analysis.
Monthly Transaction Breakdown
Analyze transactions by month with type breakdown:
const response = await fetch (
'https://api.royalti.io/accounting/transactions/monthly?start=2024-01-01&stop=2024-12-31' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const monthlyData = await response . json ();
monthlyData . forEach ( month => {
console . log ( ` ${ month . month } :` );
console . log ( ` Payments: $ ${ month . transactions . payment } ` );
console . log ( ` Revenue: $ ${ month . transactions . revenue } ` );
console . log ( ` Expenses: $ ${ month . transactions . expense } ` );
console . log ( ` Net: $ ${ month . transactions . net } ` );
});
response = requests.get(
'https://api.royalti.io/accounting/transactions/monthly?start=2024-01-01&stop=2024-12-31' ,
headers = headers
)
monthly_data = response.json()
for month in monthly_data:
print ( f " { month[ 'month' ] } :" )
print ( f " Payments: $ { month[ 'transactions' ][ 'payment' ] } " )
print ( f " Revenue: $ { month[ 'transactions' ][ 'revenue' ] } " )
print ( f " Expenses: $ { month[ 'transactions' ][ 'expense' ] } " )
print ( f " Net: $ { month[ 'transactions' ][ 'net' ] } " )
Query Parameters:
start - Start date (ISO format: YYYY-MM-DD)
stop - End date (ISO format: YYYY-MM-DD)
Response Format:
[
{
"month" : "2024-08-01" ,
"transactions" : {
"payment" : 5000.00 ,
"revenue" : 12500.50 ,
"expense" : 1500.25 ,
"net" : 6000.25
}
}
]
Expense Management
Track costs associated with releases, artists, and operations. Expenses reduce user earnings in accounting calculations.
Flexible Expense Association
Expenses can be associated with different types of entities in your catalog:
Type Description Use Case userAssociate with a specific user Personal expenses, reimbursements artistAssociate with an artist Recording sessions, promotional costs productAssociate with an album/EP Marketing campaigns, manufacturing assetAssociate with a single track Mastering, mixing, feature artist fees generalNot associated with any entity Office expenses, software subscriptions
How It Works:
Specify the type field to indicate the entity type
Provide the id field with the entity’s UUID
For general expenses, omit the id field
This flexible system allows you to:
Track costs at any level of granularity
Generate expense reports by artist, product, or asset
Allocate expenses to the appropriate earnings calculations
Create Product Expense
const response = await fetch ( 'https://api.royalti.io/expense/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
title: 'Marketing Campaign' ,
type: 'product' ,
id: 'product-uuid' ,
transactionDate: '2024-03-15' ,
currency: 'USD' ,
amount: 2500.00 ,
amountUSD: 2500.00 ,
conversionRate: 1.0 ,
memo: 'Social media advertising for album release' ,
split: [
{ user: 'user-id-1' , share: 60 },
{ user: 'user-id-2' , share: 40 }
]
})
});
const expense = await response . json ();
response = requests.post(
'https://api.royalti.io/expense/' ,
headers = headers,
json = {
'title' : 'Marketing Campaign' ,
'type' : 'product' ,
'id' : 'product-uuid' ,
'transactionDate' : '2024-03-15' ,
'currency' : 'USD' ,
'amount' : 2500.00 ,
'amountUSD' : 2500.00 ,
'memo' : 'Social media advertising for album release' ,
'split' : [
{ 'user' : 'user-id-1' , 'share' : 60 },
{ 'user' : 'user-id-2' , 'share' : 40 }
]
}
)
expense = response.json()
Create Artist Expense
const response = await fetch ( 'https://api.royalti.io/expense/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
title: 'Studio Recording Session' ,
type: 'artist' ,
id: 'artist-uuid' ,
transactionDate: '2024-02-20' ,
currency: 'USD' ,
amount: 5000.00 ,
amountUSD: 5000.00 ,
memo: 'Recording sessions for new album'
})
});
response = requests.post(
'https://api.royalti.io/expense/' ,
headers = headers,
json = {
'title' : 'Studio Recording Session' ,
'type' : 'artist' ,
'id' : 'artist-uuid' ,
'transactionDate' : '2024-02-20' ,
'currency' : 'USD' ,
'amount' : 5000.00 ,
'amountUSD' : 5000.00 ,
'memo' : 'Recording sessions for new album'
}
)
expense = response.json()
Create Asset Expense
Track costs for individual tracks or recordings:
const response = await fetch ( 'https://api.royalti.io/expense/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
title: 'Mastering Services' ,
type: 'asset' ,
id: 'asset-uuid' ,
transactionDate: '2024-03-10' ,
currency: 'USD' ,
amount: 800.00 ,
amountUSD: 800.00 ,
memo: 'Professional mastering for single track'
})
});
response = requests.post(
'https://api.royalti.io/expense/' ,
headers = headers,
json = {
'title' : 'Mastering Services' ,
'type' : 'asset' ,
'id' : 'asset-uuid' ,
'transactionDate' : '2024-03-10' ,
'currency' : 'USD' ,
'amount' : 800.00 ,
'amountUSD' : 800.00 ,
'memo' : 'Professional mastering for single track'
}
)
expense = response.json()
Create General Expense
const response = await fetch ( 'https://api.royalti.io/expense/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
title: 'Legal Consultation' ,
type: 'general' ,
transactionDate: '2024-03-01' ,
currency: 'USD' ,
amount: 1500.00 ,
amountUSD: 1500.00 ,
memo: 'Contract review and negotiation'
})
});
response = requests.post(
'https://api.royalti.io/expense/' ,
headers = headers,
json = {
'title' : 'Legal Consultation' ,
'type' : 'general' ,
'transactionDate' : '2024-03-01' ,
'currency' : 'USD' ,
'amount' : 1500.00 ,
'amountUSD' : 1500.00 ,
'memo' : 'Contract review and negotiation'
}
)
expense = response.json()
List Expenses
const response = await fetch (
'https://api.royalti.io/expense/?include=Artist,Product,Asset&page=1&size=20' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const { expenses } = await response . json ();
expenses . forEach ( expense => {
console . log ( ` ${ expense . title } : $ ${ expense . amountUSD } ` );
if ( expense . Artist ) console . log ( ` Artist: ${ expense . Artist . name } ` );
if ( expense . Product ) console . log ( ` Product: ${ expense . Product . title } ` );
});
response = requests.get(
'https://api.royalti.io/expense/?include=Artist,Product,Asset&page=1&size=20' ,
headers = headers
)
data = response.json()
expenses = data[ 'expenses' ]
for expense in expenses:
print ( f " { expense[ 'title' ] } : $ { expense[ 'amountUSD' ] } " )
if 'Artist' in expense and expense[ 'Artist' ]:
print ( f " Artist: { expense[ 'Artist' ][ 'name' ] } " )
if 'Product' in expense and expense[ 'Product' ]:
print ( f " Product: { expense[ 'Product' ][ 'title' ] } " )
Expense Splits
Distribute expenses among multiple users:
{
split : [
{ user: 'user-uuid-1' , share: 50 }, // 50% of expense
{ user: 'user-uuid-2' , share: 30 }, // 30% of expense
{ user: 'user-uuid-3' , share: 20 } // 20% of expense
]
}
expense_data = {
'split' : [
{ 'user' : 'user-uuid-1' , 'share' : 50 }, # 50% of expense
{ 'user' : 'user-uuid-2' , 'share' : 30 }, # 30% of expense
{ 'user' : 'user-uuid-3' , 'share' : 20 } # 20% of expense
]
}
Expense splits reduce user earnings in accounting calculations. The total share should equal 100.
Bulk Expense Creation
Create multiple expenses in a single request:
const expenseData = [
{
title: 'Marketing - Product A' ,
type: 'product' ,
id: 'product-a-uuid' ,
transactionDate: '2024-03-15' ,
currency: 'USD' ,
amount: 1000.00 ,
amountUSD: 1000.00
},
{
title: 'Marketing - Product B' ,
type: 'product' ,
id: 'product-b-uuid' ,
transactionDate: '2024-03-15' ,
currency: 'USD' ,
amount: 1500.00 ,
amountUSD: 1500.00
}
];
const response = await fetch ( 'https://api.royalti.io/expense/bulk' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({ expenses: expenseData })
});
const result = await response . json ();
console . log ( 'Created:' , result . data . created );
console . log ( 'Failed:' , result . data . failed );
expense_data = [
{
'title' : 'Marketing - Product A' ,
'type' : 'product' ,
'id' : 'product-a-uuid' ,
'transactionDate' : '2024-03-15' ,
'currency' : 'USD' ,
'amount' : 1000.00 ,
'amountUSD' : 1000.00
},
{
'title' : 'Marketing - Product B' ,
'type' : 'product' ,
'id' : 'product-b-uuid' ,
'transactionDate' : '2024-03-15' ,
'currency' : 'USD' ,
'amount' : 1500.00 ,
'amountUSD' : 1500.00
}
]
response = requests.post(
'https://api.royalti.io/expense/bulk' ,
headers = headers,
json = { 'expenses' : expense_data}
)
result = response.json()
print ( f "Created: { result[ 'data' ][ 'created' ] } " )
print ( f "Failed: { result[ 'data' ][ 'failed' ] } " )
Revenue Management
Track additional income sources beyond streaming royalties.
Create Sync Licensing Revenue
const response = await fetch ( 'https://api.royalti.io/revenue/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
title: 'Sync Licensing Deal' ,
amount: 10000.00 ,
currency: 'USD' ,
amountUSD: 10000.00 ,
transactionDate: '2024-03-20T10:00:00Z' ,
ArtistId: 'artist-uuid' ,
memo: 'TV commercial licensing' ,
splits: [
{ user: 'user-id-1' , share: 60 },
{ user: 'user-id-2' , share: 40 }
]
})
});
const revenue = await response . json ();
response = requests.post(
'https://api.royalti.io/revenue/' ,
headers = headers,
json = {
'title' : 'Sync Licensing Deal' ,
'amount' : 10000.00 ,
'currency' : 'USD' ,
'amountUSD' : 10000.00 ,
'transactionDate' : '2024-03-20T10:00:00Z' ,
'ArtistId' : 'artist-uuid' ,
'memo' : 'TV commercial licensing' ,
'splits' : [
{ 'user' : 'user-id-1' , 'share' : 60 },
{ 'user' : 'user-id-2' , 'share' : 40 }
]
}
)
revenue = response.json()
Create Merchandise Revenue
const response = await fetch ( 'https://api.royalti.io/revenue/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
title: 'Merchandise Sales - Q1' ,
amount: 5000.00 ,
currency: 'USD' ,
amountUSD: 5000.00 ,
transactionDate: '2024-03-31T23:59:59Z' ,
memo: 'T-shirts and posters sales'
})
});
response = requests.post(
'https://api.royalti.io/revenue/' ,
headers = headers,
json = {
'title' : 'Merchandise Sales - Q1' ,
'amount' : 5000.00 ,
'currency' : 'USD' ,
'amountUSD' : 5000.00 ,
'transactionDate' : '2024-03-31T23:59:59Z' ,
'memo' : 'T-shirts and posters sales'
}
)
revenue = response.json()
List Revenue
const response = await fetch (
'https://api.royalti.io/revenue/?include=Artist&page=1&size=20' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const { revenues } = await response . json ();
revenues . forEach ( revenue => {
console . log ( ` ${ revenue . title } : $ ${ revenue . amountUSD } ` );
if ( revenue . Artist ) console . log ( ` Artist: ${ revenue . Artist . name } ` );
});
response = requests.get(
'https://api.royalti.io/revenue/?include=Artist&page=1&size=20' ,
headers = headers
)
data = response.json()
revenues = data[ 'revenues' ]
for revenue in revenues:
print ( f " { revenue[ 'title' ] } : $ { revenue[ 'amountUSD' ] } " )
if 'Artist' in revenue and revenue[ 'Artist' ]:
print ( f " Artist: { revenue[ 'Artist' ][ 'name' ] } " )
Bulk Revenue Creation
Create multiple revenue records in a single request:
const revenueData = [
{
title: 'Sync Deal A' ,
amount: 5000.00 ,
currency: 'USD' ,
amountUSD: 5000.00 ,
transactionDate: '2024-03-01' ,
ArtistId: 'artist-a-uuid'
},
{
title: 'Sync Deal B' ,
amount: 7500.00 ,
currency: 'USD' ,
amountUSD: 7500.00 ,
transactionDate: '2024-03-15' ,
ArtistId: 'artist-b-uuid'
}
];
const response = await fetch ( 'https://api.royalti.io/revenue/bulk' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({ revenues: revenueData })
});
revenue_data = [
{
'title' : 'Sync Deal A' ,
'amount' : 5000.00 ,
'currency' : 'USD' ,
'amountUSD' : 5000.00 ,
'transactionDate' : '2024-03-01' ,
'ArtistId' : 'artist-a-uuid'
},
{
'title' : 'Sync Deal B' ,
'amount' : 7500.00 ,
'currency' : 'USD' ,
'amountUSD' : 7500.00 ,
'transactionDate' : '2024-03-15' ,
'ArtistId' : 'artist-b-uuid'
}
]
response = requests.post(
'https://api.royalti.io/revenue/bulk' ,
headers = headers,
json = { 'revenues' : revenue_data}
)
result = response.json()
Accounting Operations
When to Refresh Accounting Data
Accounting data is automatically calculated when:
Royalty files are uploaded and processed
Splits are created or modified
Payments are recorded
Expenses or revenue entries are created
You may want to manually refresh when:
You’ve made multiple split changes
You need guaranteed latest data
You suspect calculations are out of sync
Refresh for Specific User
Request updated calculations for a single user:
const response = await fetch (
`https://api.royalti.io/accounting/users/ ${ userId } /recalculate?forceRefresh=true` ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const result = await response . json ();
console . log ( 'Status:' , result . message );
console . log ( 'Job ID:' , result . jobId );
response = requests.post(
f 'https://api.royalti.io/accounting/users/ { user_id } /recalculate?forceRefresh=true' ,
headers = headers
)
result = response.json()
print ( f "Status: { result[ 'message' ] } " )
print ( f "Job ID: { result[ 'jobId' ] } " )
Query Parameters:
forceRefresh=true - Force recalculation even if data is recent
Response:
{
"message" : "User accounting update queued successfully" ,
"jobId" : "job-uuid-123" ,
"userId" : "tenant-user-id" ,
"status" : "queued"
}
Refresh All Users in Workspace
Request recalculation for all users:
const response = await fetch (
'https://api.royalti.io/accounting/tenant/recalculate?batchSize=100' ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const result = await response . json ();
console . log ( 'Status:' , result . message );
console . log ( 'Job ID:' , result . jobId );
response = requests.post(
'https://api.royalti.io/accounting/tenant/recalculate?batchSize=100' ,
headers = headers
)
result = response.json()
print ( f "Status: { result[ 'message' ] } " )
print ( f "Job ID: { result[ 'jobId' ] } " )
Query Parameters:
batchSize - Users per batch (default: 100, min: 1, max: 500)
Response:
{
"message" : "Tenant accounting update queued successfully" ,
"jobId" : "job-uuid-456" ,
"tenantId" : 123 ,
"status" : "queued" ,
"info" : {
"batchSize" : 100 ,
"estimatedTime" : "~5 minutes for 500 users"
}
}
For workspaces with many users (100+), recalculation is processed in batches to ensure reliability. Check the status endpoint to monitor progress.
For small workspaces with fewer than 100 users, you can use the immediate refresh:
const response = await fetch (
'https://api.royalti.io/accounting/refresh' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const result = await response . json ();
console . log ( 'Processed:' , result . processedCount );
console . log ( 'Total users:' , result . totalUsers );
response = requests.get(
'https://api.royalti.io/accounting/refresh' ,
headers = headers
)
result = response.json()
print ( f "Processed: { result[ 'processedCount' ] } " )
print ( f "Total users: { result[ 'totalUsers' ] } " )
Response:
{
"message" : "success" ,
"processedCount" : 125 ,
"totalUsers" : 125
}
This endpoint processes all users immediately. For workspaces with 100+ users, use the batched recalculate endpoint instead to avoid timeouts.
Check Recalculation Status
Monitor the progress of recalculation operations:
const response = await fetch (
`https://api.royalti.io/accounting/queue/status?jobId= ${ jobId } ` ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const status = await response . json ();
console . log ( 'Job state:' , status . state );
console . log ( 'Progress:' , status . progress + '%' );
response = requests.get(
f 'https://api.royalti.io/accounting/queue/status?jobId= { job_id } ' ,
headers = headers
)
status = response.json()
print ( f "Job state: { status[ 'state' ] } " )
print ( f "Progress: { status[ 'progress' ] } %" )
Job States:
waiting - Job queued, not yet started
active - Currently processing
completed - Successfully finished
failed - Encountered an error
delayed - Temporarily delayed, will retry
Response Example:
{
"jobId" : "job-uuid-123" ,
"state" : "active" ,
"progress" : 45.5 ,
"data" : {
"triggeredBy" : "manual" ,
"timestamp" : "2024-08-11T10:30:00Z" ,
"userCount" : 100
},
"timestamps" : {
"created" : 1723370000000 ,
"processed" : 1723370050000 ,
"finished" : null
}
}
Queue Overview
View overall queue status without specifying a job ID:
const response = await fetch (
'https://api.royalti.io/accounting/queue/status' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const overview = await response . json ();
console . log ( 'Waiting jobs:' , overview . queue . waiting );
console . log ( 'Active jobs:' , overview . queue . active );
console . log ( 'Completed:' , overview . queue . completed );
console . log ( 'Failed:' , overview . queue . failed );
response = requests.get(
'https://api.royalti.io/accounting/queue/status' ,
headers = headers
)
overview = response.json()
print ( f "Waiting jobs: { overview[ 'queue' ][ 'waiting' ] } " )
print ( f "Active jobs: { overview[ 'queue' ][ 'active' ] } " )
print ( f "Completed: { overview[ 'queue' ][ 'completed' ] } " )
print ( f "Failed: { overview[ 'queue' ][ 'failed' ] } " )
Statistics Pipeline
Refresh all statistics and accounting data in the correct order:
const response = await fetch (
'https://api.royalti.io/accounting/refreshstats' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const result = await response . json ();
console . log ( 'Status:' , result . message );
console . log ( 'Flow ID:' , result . flowId );
response = requests.get(
'https://api.royalti.io/accounting/refreshstats' ,
headers = headers
)
result = response.json()
print ( f "Status: { result[ 'message' ] } " )
print ( f "Flow ID: { result[ 'flowId' ] } " )
Response:
{
"message" : "Stats and accounting refresh queued successfully" ,
"flowId" : "flow-uuid-789" ,
"tenantId" : 123 ,
"status" : "queued" ,
"info" : {
"triggeredBy" : "manual" ,
"executionOrder" : "Product Stats → Asset Stats → Artist Stats → Accounting"
}
}
This endpoint ensures all statistics are recalculated in the correct dependency order. Use this after bulk data uploads or major catalog changes.
Multi-Currency Handling
All financial records support multi-currency transactions with automatic USD conversion:
{
currency : 'EUR' ,
amount : 1000.00 , // Amount in EUR
amountUSD : 1090.00 , // USD equivalent
conversionRate : 1.09 // EUR to USD rate
}
{
'currency' : 'EUR' ,
'amount' : 1000.00 , # Amount in EUR
'amountUSD' : 1090.00 , # USD equivalent
'conversionRate' : 1.09 # EUR to USD rate
}
Example: Euro Payment:
const eurAmount = 1000 ;
const eurToUsdRate = 1.09 ; // 1 EUR = 1.09 USD
const usdAmount = eurAmount * eurToUsdRate ;
const response = await fetch ( 'https://api.royalti.io/payment/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
user: 'user-uuid' ,
title: 'Euro Payment' ,
transactionDate: '2024-03-15' ,
currency: 'EUR' ,
amount: eurAmount , // 1000
amountUSD: usdAmount , // 1090
conversionRate: eurToUsdRate // 1.09
})
});
eur_amount = 1000
eur_to_usd_rate = 1.09 # 1 EUR = 1.09 USD
usd_amount = eur_amount * eur_to_usd_rate
response = requests.post(
'https://api.royalti.io/payment/' ,
headers = headers,
json = {
'user' : 'user-uuid' ,
'title' : 'Euro Payment' ,
'transactionDate' : '2024-03-15' ,
'currency' : 'EUR' ,
'amount' : eur_amount, # 1000
'amountUSD' : usd_amount, # 1090
'conversionRate' : eur_to_usd_rate # 1.09
}
)
payment = response.json()
The system uses amountUSD for all accounting calculations to ensure consistency across currencies.
Best Practices
Data Accuracy
Trust automatic calculations - The system updates accounting when royalties upload or splits change
Use forceRefresh sparingly - Only when you need guaranteed fresh data
Validate splits first - Ensure splits total 100% before expecting accurate earnings
Regular reconciliation - Compare accounting data with actual payments monthly
Verify amounts - Always double-check USD conversion rates
Detailed memos - Include transaction context for audit trails
Use default cached data - Faster response times for most queries
Batch operations - Use bulk endpoints for multiple records
Monitor queue status - Track long-running recalculation operations
Paginate results - Use reasonable page sizes (10-50 items)
Async processing - Don’t wait for recalculation to complete
Reporting Workflows
Use date filters - Filter transactions by specific periods for reports
Export for analysis - Use transaction endpoints for external reporting tools
Monitor monthly trends - Track revenue and payment patterns over time
Check user balances - Regularly review who is owed payment
Weekly reviews - Check user balances and pending items
Monthly cycles - Establish consistent payment schedules
Quarterly audits - Reconcile all transactions against external records
Error Handling
Always implement robust error handling for financial operations:
async function refreshUserAccounting ( userId , forceRefresh = false ) {
try {
const url = `https://api.royalti.io/accounting/users/ ${ userId } /recalculate?forceRefresh= ${ forceRefresh } ` ;
const response = await fetch ( url , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } `
}
});
if ( ! response . ok ) {
const error = await response . json ();
throw new Error ( `Refresh failed: ${ error . message } ` );
}
const result = await response . json ();
// Monitor job status
const jobId = result . jobId ;
const statusUrl = `https://api.royalti.io/accounting/queue/status?jobId= ${ jobId } ` ;
let completed = false ;
while ( ! completed ) {
await new Promise ( resolve => setTimeout ( resolve , 2000 )); // Wait 2 seconds
const statusResponse = await fetch ( statusUrl , {
headers: { 'Authorization' : `Bearer ${ token } ` }
});
const status = await statusResponse . json ();
if ( status . state === 'completed' ) {
completed = true ;
console . log ( 'Refresh completed successfully' );
} else if ( status . state === 'failed' ) {
throw new Error ( 'Refresh job failed' );
}
console . log ( `Progress: ${ status . progress } %` );
}
return true ;
} catch ( error ) {
console . error ( 'Accounting refresh failed:' , error . message );
throw error ;
}
}
import requests
import time
def refresh_user_accounting ( user_id , force_refresh = False ):
try :
url = f 'https://api.royalti.io/accounting/users/ { user_id } /recalculate'
params = { 'forceRefresh' : force_refresh}
response = requests.post(url, headers = headers, params = params)
response.raise_for_status()
result = response.json()
job_id = result[ 'jobId' ]
# Monitor job status
status_url = f 'https://api.royalti.io/accounting/queue/status'
completed = False
while not completed:
time.sleep( 2 ) # Wait 2 seconds
status_response = requests.get(
status_url,
headers = headers,
params = { 'jobId' : job_id}
)
status = status_response.json()
if status[ 'state' ] == 'completed' :
completed = True
print ( 'Refresh completed successfully' )
elif status[ 'state' ] == 'failed' :
raise Exception ( 'Refresh job failed' )
print ( f "Progress: { status[ 'progress' ] } %" )
return True
except Exception as e:
print ( f 'Accounting refresh failed: { e } ' )
raise
Troubleshooting
Earnings don't match expected amount
Checklist:
Verify split configurations are active
Check split date ranges include relevant periods
Review conditional split logic (territory, DSP-specific splits)
Ensure royalty data uploaded successfully
Check for expenses reducing earnings
Solutions:
Check user stats: GET /accounting/{userId}/stats
Force refresh: GET /accounting/{userId}/stats?forceRefresh=true
Verify splits: GET /split?userId={userId}
Check royalty data: GET /royalty?userId={userId}
Review expenses: GET /expense?userId={userId}
Possible causes:
Recent split changes not yet reflected
Royalty file just uploaded
Manual refresh needed
Cache delay
Solutions:
Use forceRefresh=true parameter
Check recent uploads: GET /file?status=completed
Verify split changes saved: GET /split/{splitId}
Request recalculation: POST /accounting/users/{userId}/recalculate
Refresh job stuck or failed
Check job status: const status = await fetch (
`https://api.royalti.io/accounting/queue/status?jobId= ${ jobId } ` ,
{ headers: { 'Authorization' : `Bearer ${ token } ` } }
);
Common causes:
Invalid split data (doesn’t total 100%)
Missing royalty data
Database connection issues
Large dataset processing
Solutions:
Check error details in job status
Verify split configurations
Retry with smaller batch size
Contact support if error persists
Expected Processing Times:
Small workspaces (<100 users): 1-2 minutes
Medium workspaces (100-1000 users): 2-10 minutes
Large workspaces (1000+ users): 10-30 minutes
User missing from current due list
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)
User actually has positive balance
Verify:
Check user splits: GET /split?userId={userId}
Check user stats: GET /accounting/{userId}/stats
Verify user exists: GET /user/{userId}
Force recalculation if needed
Transaction totals don't match
Reconciliation steps: // Get transaction summary
const summary = await fetch (
'https://api.royalti.io/accounting/transactions/summary' ,
{ headers: { 'Authorization' : `Bearer ${ token } ` } }
);
// Get current due total
const due = await fetch (
'https://api.royalti.io/accounting/gettotaldue' ,
{ headers: { 'Authorization' : `Bearer ${ token } ` } }
);
// Compare values
const summaryData = await summary . json ();
const dueData = await due . json ();
console . log ( 'Summary total paid:' , summaryData . summary . totalPayments );
console . log ( 'Accounting total paid:' , dueData . totalPaid );
If mismatched:
Check for deleted payments
Verify all payments recorded correctly
Review expenses and revenue entries
Force full workspace refresh
Currency conversion incorrect
Common Mistakes:
Wrong conversion direction (USD/EUR vs EUR/USD)
Missing conversionRate
amountUSD doesn’t match calculation
Correct Example: const eurAmount = 1000 ;
const eurToUsdRate = 1.09 ; // 1 EUR = 1.09 USD
const usdAmount = eurAmount * eurToUsdRate ;
{
currency : 'EUR' ,
amount : eurAmount , // 1000
amountUSD : usdAmount , // 1090
conversionRate : eurToUsdRate // 1.09
}
Expense not reducing user earnings
Checklist:
Expense split configured correctly
User IDs in split are valid
Share percentages total 100
Expense transaction date is within accounting period
Solutions:
Verify expense: GET /expense/{id}
Check split configuration in expense record
Force user recalculation: POST /accounting/users/{userId}/recalculate
Review accounting stats: GET /accounting/{userId}/stats?forceRefresh=true
Revenue not increasing user earnings
Checklist:
Revenue splits configured correctly
User IDs in splits are valid
Share percentages total 100
Revenue amount is positive
Solutions:
Verify revenue: GET /revenue/{id}
Check split configuration
Force user recalculation: POST /accounting/users/{userId}/recalculate
Review accounting stats: GET /accounting/{userId}/stats?forceRefresh=true
API Reference
For complete endpoint documentation, see:
Accounting & Earnings:
Refresh Operations:
Expenses:
Revenue:
Related Guides:
Support
Need help with financial management?