Overview
The Royalti.io API provides a complete financial management system for tracking income, expenses, payments, and accounting across your music catalog. This guide covers all financial components and their integration.
Key Components
Payments & Payment Requests - Process royalty distributions to collaborators
Payment Settings - Configure bank accounts and payment methods
Expenses - Track costs associated with releases, artists, and operations
Revenue - Record additional income sources beyond royalty streams
Accounting - Real-time financial reporting and reconciliation
Financial Workflow
Royalties → Splits → User Earnings
↓
Payment Request Created
↓
Admin Reviews & Approves
↓
Payment Record + Receipt Generated
↓
Accounting Automatically Updated
Prerequisites
Authentication
All financial endpoints require authentication with a Bearer token.
const headers = {
'Authorization' : `Bearer ${ your_jwt_token } ` ,
'Content-Type' : 'application/json'
};
headers = {
'Authorization' : f 'Bearer { your_jwt_token } ' ,
'Content-Type' : 'application/json'
}
Required Setup
Before using financial features, ensure you have:
User accounts - Collaborators with earnings
Splits configured - Revenue distribution defined
Payment settings - Bank accounts or payment methods set up
Quick Start: Complete Payment Cycle
Check User Balances
See who has earnings ready for payment: const response = await fetch (
'https://api.royalti.io/accounting/getcurrentdue?sort=due&order=DESC' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const { Users } = await response . json ();
// Find users meeting minimum threshold
const eligible = Users . filter ( user => user . Due >= 50 );
console . log ( ` ${ eligible . length } users eligible for payment` );
User Creates Payment Request
User requests payment for their earnings: const response = await fetch ( 'https://api.royalti.io/payment-request/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ userToken } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
PaymentSettingId: 'payment-setting-uuid' ,
currency: 'USD' ,
amountUSD: 500.00 ,
amount: 500.00 ,
memo: 'Monthly payment request'
})
});
const request = await response . json ();
console . log ( 'Request Status:' , request . newpayment . status ); // "pending"
Admin Approves Request
Admin approves and creates payment with automatic receipt: const response = await fetch (
`https://api.royalti.io/payment-request/ ${ requestId } /approve` ,
{
method: 'PATCH' ,
headers: {
'Authorization' : `Bearer ${ adminToken } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
title: 'March 2024 Royalty Payment' ,
memo: 'Q1 distribution'
})
}
);
const result = await response . json ();
console . log ( 'Payment ID:' , result . payment . id );
console . log ( 'Receipt URL:' , result . payment . files [ 0 ]); // Auto-generated PDF
Verify Accounting Updated
Confirm user balance was reduced: const response = await fetch (
`https://api.royalti.io/accounting/ ${ userId } /stats` ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const stats = await response . json ();
console . log ( 'Total Earned:' , stats . Royalty_Share );
console . log ( 'Paid:' , stats . paid );
console . log ( 'Due:' , stats . due );
Payment Management
Payment Requests
Users create payment requests which admins review and approve.
Create Payment Request:
const response = await fetch ( 'https://api.royalti.io/payment-request/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
PaymentSettingId: 'setting-uuid' ,
currency: 'USD' ,
amountUSD: 1000.00 ,
amount: 1000.00 ,
memo: 'Request for Q1 earnings'
})
});
List Pending Requests:
const response = await fetch (
'https://api.royalti.io/payment-request/?status=pending&page=1&size=20' ,
{
headers: {
'Authorization' : `Bearer ${ adminToken } `
}
}
);
const { PaymentRequests } = await response . json ();
Approve with VertoFX:
For international wire transfers:
const response = await fetch (
`https://api.royalti.io/payment-request/ ${ requestId } /approve` ,
{
method: 'PATCH' ,
headers: {
'Authorization' : `Bearer ${ adminToken } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
method: 'vertofx' ,
walletId: 'verto-wallet-uuid' ,
purposeCode: 'ROYALTY_PAYMENT' ,
memo: 'International payment via VertoFX'
})
}
);
Payment Records
Payments are automatically created when requests are approved, or can be created manually.
Create Manual Payment:
const formData = new FormData ();
formData . append ( 'user' , userId );
formData . append ( 'title' , 'Q1 2024 Royalty Payment' );
formData . append ( 'transactionDate' , '2024-04-01T10:00:00Z' );
formData . append ( 'currency' , 'USD' );
formData . append ( 'amount' , 1500.00 );
formData . append ( 'amountUSD' , 1500.00 );
formData . append ( 'conversionRate' , 1.0 );
formData . append ( 'memo' , 'Quarterly distribution' );
formData . append ( 'files' , receiptFile ); // Optional receipt upload
const response = await fetch ( 'https://api.royalti.io/payment/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } `
},
body: formData
});
Query Payments:
const response = await fetch (
'https://api.royalti.io/payment/?startDate=2024-01-01&endDate=2024-03-31&user=${userId}' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const { Payments } = await response . json ();
Payment Settings
Configure payment methods for each user.
Bank Wire Transfer:
const response = await fetch ( 'https://api.royalti.io/payment-setting/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
name: 'Primary Bank Account' ,
type: 'BankWireTransfer' ,
settings: {
bankName: 'Chase Bank' ,
accountNumber: '123456789' ,
bankCode: '987654' ,
routingNumber: '021000021' ,
swiftCode: 'CHASUS33' ,
country: 'United States' ,
countryCode: 'US' ,
currency: 'USD'
},
isDefault: true ,
memo: 'Main account for USD payments'
})
});
PayPal:
const response = await fetch ( 'https://api.royalti.io/payment-setting/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
name: 'PayPal Account' ,
type: 'PayPal' ,
settings: {
paypalEmail: 'user@example.com' ,
country: 'United States' ,
countryCode: 'US' ,
currency: 'USD'
},
isDefault: false
})
});
Expense Tracking
Track costs associated with releases, artists, or general operations.
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 Expense
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' , // Product ID
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 }
]
})
});
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'
})
});
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' , // Asset (track) ID
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 = {
'Authorization' : f 'Bearer { token } ' ,
'Content-Type' : 'application/json'
},
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'
}
)
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'
})
});
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 } ` );
});
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 splits reduce user earnings in accounting calculations.
Revenue Management
Track additional income sources beyond royalty streams.
Create Revenue
Streaming 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 }
]
})
});
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'
})
});
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 ();
Accounting & Reconciliation
User Earnings
Get detailed financial statistics for a specific user:
const response = await fetch (
`https://api.royalti.io/accounting/ ${ userId } /stats?include=royalty&forceRefresh=false` ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const stats = await response . json ();
console . log ( 'Total Earned (Gross):' , stats . Royalty_Share );
console . log ( 'Total Paid:' , stats . paid );
console . log ( 'Amount Due:' , stats . due );
// Optional: Detailed royalty breakdown
if ( stats . royaltySummary ) {
console . log ( 'Royalty Breakdown:' , stats . royaltySummary );
}
// Metadata from enhanced calculator
if ( stats . metadata ) {
console . log ( 'Calculation Method:' , stats . metadata . calculationMethod );
console . log ( 'Last Calculated:' , stats . metadata . lastCalculatedAt );
console . log ( 'Simple Splits:' , stats . metadata . simpleSplitsPercentage + '%' );
console . log ( 'Calculation Time:' , stats . metadata . calculationTime + 'ms' );
}
Query Parameters:
include=royalty - Include detailed royalty breakdown from BigQuery
forceRefresh=true - Force recalculation (bypasses cache)
All Users With Balances
Get list of all users with outstanding earnings:
const response = await fetch (
'https://api.royalti.io/accounting/getcurrentdue?sort=due&order=DESC&page=1&size=50' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const { Users , totalItems } = await response . json ();
Users . forEach ( user => {
console . log ( ` ${ user . firstName } ${ user . lastName } :` );
console . log ( ` Gross: $ ${ user . Gross } ` );
console . log ( ` Paid: $ ${ user . Paid } ` );
console . log ( ` Due: $ ${ user . Due } ` );
console . log ( ` Payment Requested: ${ user . paymentRequested ? 'Yes' : 'No' } ` );
});
Query Parameters:
q - Search by user name or nickname
sort - Field to sort by (due, gross, paid, etc.)
order - Sort direction (ASC or DESC)
page / size - Pagination
Workspace Totals
Get aggregate financial summary for your workspace:
const response = await fetch (
'https://api.royalti.io/accounting/gettotaldue?method=cache' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const totals = await response . json ();
console . log ( 'Total Gross Earnings:' , totals . totalGross );
console . log ( 'Total Paid Out:' , totals . totalPaid );
console . log ( 'Total Outstanding:' , totals . totalDue );
console . log ( 'Number of Users:' , totals . userCount );
console . log ( 'Calculation Method:' , totals . metadata . method );
Method Options:
cache - Use cached data (fastest)
bigquery - Query BigQuery directly (slower, always fresh)
auto - Automatic selection (cache with BigQuery fallback)
Transaction History
Get all financial transactions (payments, revenue, expenses):
const response = await fetch (
'https://api.royalti.io/accounting/transactions?start=2024-01-01&stop=2024-03-31&page=1&size=50' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const { transactions } = await response . json ();
transactions . forEach ( txn => {
console . log ( ` ${ txn . type } : ${ txn . title } - $ ${ txn . amountUSD } ` );
console . log ( ` Date: ${ txn . transactionDate } ` );
console . log ( ` User: ${ txn . user . fullName } ` );
});
Monthly Summary
Get transactions grouped by month:
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 . payments } ` );
console . log ( ` Revenue: $ ${ month . revenue } ` );
console . log ( ` Expenses: $ ${ month . expenses } ` );
console . log ( ` Net: $ ${ month . net } ` );
});
Transaction Summary
Get aggregate statistics:
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 Revenue:' , summary . totalRevenues );
console . log ( 'Total Expenses:' , summary . totalExpenses );
console . log ( 'Net Amount:' , summary . netAmount );
Advanced Features
Recalculating Accounting
Force recalculation of user earnings:
Single 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 );
All Users (Tenant-Wide):
const response = await fetch (
'https://api.royalti.io/accounting/tenant/recalculate' ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const { jobId } = await response . json ();
Tenant-wide recalculation can take several minutes for large datasets. The operation runs in the background.
Monitor Recalculation Progress
Check the status of a recalculation job:
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 . job . state ); // waiting, active, completed, failed
console . log ( 'Progress:' , status . job . progress ); // Percentage complete
console . log ( 'Processed:' , status . job . processedUsers );
console . log ( 'Total:' , status . job . totalUsers );
if ( status . job . estimatedTimeRemaining ) {
console . log ( 'ETA:' , status . job . estimatedTimeRemaining + 's' );
}
Multi-Currency Handling
All financial records support multi-currency:
{
currency : 'EUR' ,
amount : 1000.00 , // Amount in EUR
amountUSD : 1090.00 , // USD equivalent
conversionRate : 1.09 // EUR to USD rate
}
The system uses amountUSD for all accounting calculations to ensure consistency across currencies.
Bulk Operations
Bulk Payment Creation:
const paymentData = [
{
user: 'user-id-1' ,
title: 'March 2024 Royalties' ,
transactionDate: '2024-04-01T10:00:00Z' ,
currency: 'USD' ,
amount: 500.00 ,
amountUSD: 500.00
},
{
user: 'user-id-2' ,
title: 'March 2024 Royalties' ,
transactionDate: '2024-04-01T10:00:00Z' ,
currency: 'EUR' ,
amount: 400.00 ,
amountUSD: 436.00 ,
conversionRate: 1.09
}
];
const response = await fetch ( 'https://api.royalti.io/payment/bulk' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({ payments: paymentData })
});
const result = await response . json ();
console . log ( 'Created:' , result . data . created );
console . log ( 'Failed:' , result . data . failed );
console . log ( 'Errors:' , result . data . errors );
Bulk Expense Creation:
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 })
});
Best Practices
Regular Reconciliation
Weekly Reviews - Check user balances and pending requests
Monthly Payments - Establish consistent payment cycles
Quarterly Audits - Reconcile all transactions against external records
Annual Reports - Generate comprehensive financial reports
Data Accuracy
Verify Amounts - Always double-check USD conversion rates
Attach Receipts - Upload proof of payment for all transactions
Detailed Memos - Include payment period, method, and context
Consistent Dates - Use transaction dates, not processing dates
Use Cache - Default to cache-first for accounting queries
Batch Operations - Use bulk endpoints for multiple records
Monitor Jobs - Track long-running recalculation progress
Async Processing - Don’t wait for recalculation to complete
Error Handling
Always implement robust error handling:
async function createPaymentSafely ( paymentData ) {
try {
const response = await fetch ( 'https://api.royalti.io/payment/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ( paymentData )
});
if ( ! response . ok ) {
const error = await response . json ();
throw new Error ( `Payment failed: ${ error . message } ` );
}
const payment = await response . json ();
// Verify accounting updated
const accountingResponse = await fetch (
`https://api.royalti.io/accounting/ ${ paymentData . user } /stats` ,
{ headers: { 'Authorization' : `Bearer ${ token } ` } }
);
const stats = await accountingResponse . json ();
console . log ( 'Balance updated. Due:' , stats . due );
return payment ;
} catch ( error ) {
console . error ( 'Payment creation failed:' , error . message );
// Implement retry logic or alert admins
throw error ;
}
}
Troubleshooting
User balance not updating after payment
Checklist:
Payment successfully created (check response)
Correct user ID used
Amount is positive
Payment linked to correct workspace
Solutions:
Verify payment: GET /payment/{id}
Check user stats: GET /accounting/{userId}/stats
Force refresh: GET /accounting/{userId}/stats?forceRefresh=true
If still wrong, recalculate: POST /accounting/users/{userId}/recalculate
Accounting totals don't match payments
Common Causes:
Deleted payments not recalculated
Split changes not reflected
Expenses or revenue not considered
Solutions:
Check transaction summary: GET /accounting/transactions/summary
Compare with payment records: GET /payment/?startDate=...&endDate=...
Force tenant recalculation: POST /accounting/tenant/recalculate
Payment request stuck in pending
Possible Reasons:
Awaiting admin approval
User has another pending request
Payment setting invalid
Solutions:
Check request status: GET /payment-request/{id}
Verify payment setting: GET /payment-setting/{settingId}
Approve or decline: PATCH /payment-request/{id}/approve or decline
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
}
Recalculation taking too long
Expected Behavior:
Small workspaces (<100 users): 1-2 minutes
Medium workspaces (100-1000 users): 2-10 minutes
Large workspaces (1000+ users): 10-30 minutes
Solutions:
Check progress: GET /accounting/queue/status?jobId={jobId}
Be patient - jobs process in background
If stuck > 1 hour, check queue health
Only one recalculation per tenant at a time
API Reference
For complete endpoint documentation, see:
Payments:
Payment Requests:
Payment Settings:
Expenses:
Revenue:
Accounting:
Related Guides: