Skip to main content

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.
Node.js
const headers = {
  'Authorization': `Bearer ${your_jwt_token}`,
  'Content-Type': 'application/json'
};
Python
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

1

Check User Balances

See who has earnings ready for payment:
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();

// Find users meeting minimum threshold
const eligible = Users.filter(user => user.Due >= 50);
console.log(`${eligible.length} users eligible for payment`);
2

User Creates Payment Request

User requests payment for their earnings:
Node.js
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"
3

Admin Approves Request

Admin approves and creates payment with automatic receipt:
Node.js
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
4

Verify Accounting Updated

Confirm user balance was reduced:
Node.js
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:
Node.js
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:
Node.js
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:
Node.js
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'
    })
  }
);
See the Payment Processing Guide for detailed payment request workflows.

Payment Records

Payments are automatically created when requests are approved, or can be created manually. Create Manual Payment:
Node.js
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:
Node.js
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:
Node.js
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:
Node.js
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:
TypeDescriptionUse Case
userAssociate with a specific userPersonal expenses, reimbursements
artistAssociate with an artistRecording sessions, promotional costs
productAssociate with an album/EPMarketing campaigns, manufacturing
assetAssociate with a single trackMastering, mixing, feature artist fees
generalNot associated with any entityOffice 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:
Node.js
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:
Node.js
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:
Node.js
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'
  })
});
Python
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:
Node.js
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

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

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

  1. Weekly Reviews - Check user balances and pending requests
  2. Monthly Payments - Establish consistent payment cycles
  3. Quarterly Audits - Reconcile all transactions against external records
  4. Annual Reports - Generate comprehensive financial reports

Data Accuracy

  1. Verify Amounts - Always double-check USD conversion rates
  2. Attach Receipts - Upload proof of payment for all transactions
  3. Detailed Memos - Include payment period, method, and context
  4. Consistent Dates - Use transaction dates, not processing dates

Performance Optimization

  1. Use Cache - Default to cache-first for accounting queries
  2. Batch Operations - Use bulk endpoints for multiple records
  3. Monitor Jobs - Track long-running recalculation progress
  4. Async Processing - Don’t wait for recalculation to complete

Error Handling

Always implement robust error handling:
Node.js
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

Checklist:
  • Payment successfully created (check response)
  • Correct user ID used
  • Amount is positive
  • Payment linked to correct workspace
Solutions:
  1. Verify payment: GET /payment/{id}
  2. Check user stats: GET /accounting/{userId}/stats
  3. Force refresh: GET /accounting/{userId}/stats?forceRefresh=true
  4. If still wrong, recalculate: POST /accounting/users/{userId}/recalculate
Common Causes:
  • Deleted payments not recalculated
  • Split changes not reflected
  • Expenses or revenue not considered
Solutions:
  1. Check transaction summary: GET /accounting/transactions/summary
  2. Compare with payment records: GET /payment/?startDate=...&endDate=...
  3. Force tenant recalculation: POST /accounting/tenant/recalculate
Possible Reasons:
  • Awaiting admin approval
  • User has another pending request
  • Payment setting invalid
Solutions:
  1. Check request status: GET /payment-request/{id}
  2. Verify payment setting: GET /payment-setting/{settingId}
  3. Approve or decline: PATCH /payment-request/{id}/approve or decline
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
}
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:
  1. Check progress: GET /accounting/queue/status?jobId={jobId}
  2. Be patient - jobs process in background
  3. If stuck > 1 hour, check queue health
  4. 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: