Overview
The Royalti.io API provides comprehensive payment management capabilities for distributing royalty earnings to collaborators. This guide covers creating payment requests, recording payments, tracking payment history, and managing payment settings.
Key Features
Payment requests - Users can request payments for their earnings
Payment records - Track all payment transactions with receipts
Multi-currency support - Handle payments in various currencies with automatic USD conversion
Payment settings - Configure minimum thresholds and payment methods per user
Bulk operations - Process multiple payments efficiently
Prerequisites
Required Setup
Before processing payments, ensure you have:
User earnings data - Calculated royalties with amounts due to users
Payment settings configured - Each user should have payment preferences
Payment method integration - Connect your payment provider (bank transfer, PayPal, Wise, etc.)
Authentication
All payment 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'
}
Quick Start: Payment Workflow
Check Who's Owed Payment
Get list of users with outstanding balances. const response = await fetch (
'https://api.royalti.io/accounting/getcurrentdue?sort=due&order=DESC' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const { Users } = await response . json ();
// Filter users meeting minimum threshold
const eligible = Users . filter ( user => user . Due >= 50 );
response = requests.get(
'https://api.royalti.io/accounting/getcurrentdue?sort=due&order=DESC' ,
headers = headers
)
data = response.json()
users = data[ 'Users' ]
# Filter users meeting minimum threshold
eligible = [u for u in users if u[ 'Due' ] >= 50 ]
Create Payment Record
Record a payment transaction with details and optional receipt upload. const formData = new FormData ();
formData . append ( 'user' , userId );
formData . append ( 'title' , 'Q1 2024 Royalty Payment' );
formData . append ( 'transactionDate' , '2024-04-15T10:00:00Z' );
formData . append ( 'currency' , 'USD' );
formData . append ( 'amount' , 1500.00 );
formData . append ( 'amountUSD' , 1500.00 );
formData . append ( 'conversionRate' , 1.0 );
formData . append ( 'memo' , 'Q1 royalty distribution' );
// Optional: attach receipt file
formData . append ( 'files' , receiptFile );
const response = await fetch ( 'https://api.royalti.io/payment/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } `
// Note: Don't set Content-Type for FormData
},
body: formData
});
const payment = await response . json ();
console . log ( 'Payment ID:' , payment . data . id );
files = {
'user' : ( None , user_id),
'title' : ( None , 'Q1 2024 Royalty Payment' ),
'transactionDate' : ( None , '2024-04-15T10:00:00Z' ),
'currency' : ( None , 'USD' ),
'amount' : ( None , '1500.00' ),
'amountUSD' : ( None , '1500.00' ),
'conversionRate' : ( None , '1.0' ),
'memo' : ( None , 'Q1 royalty distribution' ),
'files' : ( 'receipt.pdf' , open ( 'receipt.pdf' , 'rb' ), 'application/pdf' )
}
response = requests.post(
'https://api.royalti.io/payment/' ,
headers = { 'Authorization' : f 'Bearer { token } ' },
files = files
)
payment = response.json()
print ( f "Payment ID: { payment[ 'data' ][ 'id' ] } " )
Verify Accounting Updated
Check that the user’s 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 ( 'Amount paid:' , stats . paid );
console . log ( 'Amount due:' , stats . due );
When a payment is created, the user’s “paid” amount automatically increases and “due” amount decreases.
Payment Requests
Users can create payment requests for their earnings. Admins can review and approve these requests.
User Creates Payment Request
Users request payment for their available balance. API keys and admins can create requests on behalf of users.
try {
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' , // Optional: required for VertoFX or standard payment processing
currency: 'USD' ,
amountUSD: 500.00 ,
amount: 500.00 ,
memo: 'Monthly payment request'
})
});
if ( ! response . ok ) {
const error = await response . json ();
throw new Error ( `Request failed: ${ error . message } ` );
}
const request = await response . json ();
console . log ( 'Request ID:' , request . data . id );
console . log ( 'Status:' , request . data . status ); // "pending"
console . log ( 'Amount:' , request . data . amountUSD , request . data . currency );
} catch ( error ) {
console . error ( 'Failed to create payment request:' , error . message );
}
try :
response = requests.post(
'https://api.royalti.io/payment-request/' ,
headers = {
'Authorization' : f 'Bearer { user_token } ' ,
'Content-Type' : 'application/json'
},
json = {
'PaymentSettingId' : 'payment-setting-uuid' ,
'currency' : 'USD' ,
'amountUSD' : 500.00 ,
'amount' : 500.00 ,
'memo' : 'Monthly payment request'
}
)
response.raise_for_status()
request = response.json()
print ( f "Request ID: { request[ 'data' ][ 'id' ] } " )
print ( f "Status: { request[ 'data' ][ 'status' ] } " )
print ( f "Amount: { request[ 'data' ][ 'amountUSD' ] } { request[ 'data' ][ 'currency' ] } " )
except requests.exceptions.RequestException as e:
print ( f "Failed to create payment request: { e } " )
Important: While PaymentSettingId is technically optional during creation, it must be provided before approval if you intend to process the payment via standard methods or VertoFX. Approving without a PaymentSettingId will create a payment record but won’t be able to process it through any payment method.
Payment Request Fields:
PaymentSettingId (Optional) - Reference to a configured payment method. Required for VertoFX or standard payment processing during approval.
currency (Required) - Currency code (USD, EUR, GBP, etc.)
amountUSD (Required) - USD amount for accounting and reporting
amount (Optional) - Amount in the specified currency
memo (Optional) - Description of the payment request
Constraint: Users can only have one pending payment request at a time
Status flow: pending → approved or declined (terminal states)
Create for Specific User (Admin/API Key)
Admins and workspace API keys can create payment requests on behalf of specific users:
try {
const response = await fetch ( 'https://api.royalti.io/payment-request/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ apiKeyOrAdminToken } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
user: 'target-user-uuid' , // Specify the user to create request for
PaymentSettingId: 'payment-setting-uuid' ,
currency: 'USD' ,
amountUSD: 500.00 ,
amount: 500.00 ,
memo: 'Monthly royalty distribution'
})
});
if ( ! response . ok ) {
const error = await response . json ();
throw new Error ( `Request failed: ${ error . message } ` );
}
const request = await response . json ();
console . log ( `Created request for user ${ request . data . TenantUserId } ` );
} catch ( error ) {
console . error ( 'Failed to create payment request:' , error . message );
}
try :
response = requests.post(
'https://api.royalti.io/payment-request/' ,
headers = {
'Authorization' : f 'Bearer { api_key_or_admin_token } ' ,
'Content-Type' : 'application/json'
},
json = {
'user' : 'target-user-uuid' , # Specify the user
'PaymentSettingId' : 'payment-setting-uuid' ,
'currency' : 'USD' ,
'amountUSD' : 500.00 ,
'amount' : 500.00 ,
'memo' : 'Monthly royalty distribution'
}
)
response.raise_for_status()
request = response.json()
print ( f "Created request for user { request[ 'data' ][ 'TenantUserId' ] } " )
except requests.exceptions.RequestException as e:
print ( f "Failed to create payment request: { e } " )
Admin/API Key Only: Only workspace admins and API keys can specify a different user. Regular users creating payment requests will always create for themselves, even if a user parameter is provided.
List All Payment Requests
Admins can view all payment requests:
const response = await fetch (
'https://api.royalti.io/payment-request/?status=pending&page=1&size=20' ,
{
headers: {
'Authorization' : `Bearer ${ adminToken } `
}
}
);
const { requests , pagination } = await response . json ();
Query Parameters:
status - Filter by status: pending, approved, declined, processing, completed
userId - Filter by specific user
page - Page number (default: 1)
size - Items per page (default: 20)
Approve Payment Request
When you approve a payment request, the system automatically:
Creates a Payment record
Generates a PDF receipt
Uploads the receipt to cloud storage
Updates the request status to “approved”
Optionally processes international payments via VertoFX
Basic approval creates a payment record with automatic receipt generation: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' ,
transactionDate: '2024-04-01T10:00:00Z' ,
memo: 'Q1 royalty distribution'
})
}
);
const result = await response . json ();
console . log ( 'Status:' , result . paymentRequest . status ); // "approved"
console . log ( 'Payment ID:' , result . payment . id );
console . log ( 'Receipt URL:' , result . payment . files [ 0 ]); // Auto-generated PDF
response = requests.patch(
f 'https://api.royalti.io/payment-request/ { request_id } /approve' ,
headers = {
'Authorization' : f 'Bearer { admin_token } ' ,
'Content-Type' : 'application/json'
},
json = {
'title' : 'March 2024 Royalty Payment' ,
'transactionDate' : '2024-04-01T10:00:00Z' ,
'memo' : 'Q1 royalty distribution'
}
)
result = response.json()
print ( f "Status: { result[ 'paymentRequest' ][ 'status' ] } " )
print ( f "Receipt URL: { result[ 'payment' ][ 'files' ][ 0 ] } " )
All optional parameters default to the values from the payment request if not provided.
International payments via VertoFX for wire transfers to beneficiaries worldwide: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' ,
title: 'International Royalty Payment' ,
memo: 'Paid via VertoFX international transfer'
})
}
);
const result = await response . json ();
console . log ( 'VertoFX Transfer ID:' , result . vertoTransferId );
console . log ( 'Status:' , result . paymentRequest . status );
response = requests.patch(
f 'https://api.royalti.io/payment-request/ { request_id } /approve' ,
headers = {
'Authorization' : f 'Bearer { admin_token } ' ,
'Content-Type' : 'application/json'
},
json = {
'method' : 'vertofx' ,
'walletId' : 'verto-wallet-uuid' ,
'purposeCode' : 'ROYALTY_PAYMENT' ,
'title' : 'International Royalty Payment' ,
'memo' : 'Paid via VertoFX international transfer'
}
)
result = response.json()
print ( f "VertoFX Transfer ID: { result.get( 'vertoTransferId' ) } " )
Required for VertoFX:
method: Must be set to 'vertofx'
walletId: Your VertoFX wallet ID
purposeCode: Payment purpose code (e.g., ‘ROYALTY_PAYMENT’, ‘INVOICE_PAYMENT’)
Upload additional receipts or supporting documents:const formData = new FormData ();
formData . append ( 'title' , 'March 2024 Payment' );
formData . append ( 'memo' , 'Payment with bank receipt attached' );
formData . append ( 'files' , bankReceiptFile ); // File object
const response = await fetch (
`https://api.royalti.io/payment-request/ ${ requestId } /approve` ,
{
method: 'PATCH' ,
headers: {
'Authorization' : `Bearer ${ adminToken } `
// Don't set Content-Type for FormData
},
body: formData
}
);
const result = await response . json ();
console . log ( 'Files:' , result . payment . files );
// Contains both auto-generated PDF and uploaded receipt
files = {
'title' : ( None , 'March 2024 Payment' ),
'memo' : ( None , 'Payment with bank receipt attached' ),
'files' : ( 'receipt.pdf' , open ( 'receipt.pdf' , 'rb' ), 'application/pdf' )
}
response = requests.patch(
f 'https://api.royalti.io/payment-request/ { request_id } /approve' ,
headers = { 'Authorization' : f 'Bearer { admin_token } ' },
files = files
)
result = response.json()
print ( f "Files: { result[ 'payment' ][ 'files' ] } " )
Approval Parameters:
Parameter Type Required Description title string No Payment title (defaults to request title) transactionDate string No Transaction date (defaults to current date) currency string No Currency code (defaults to request currency) amount number No Payment amount (defaults to request amount) amountUSD number No USD amount (defaults to request amountUSD) conversionRate number No Exchange rate memo string No Additional notes method string No Payment method (‘vertofx’ for international) walletId string Conditional Required if method=‘vertofx’ purposeCode string Conditional Required if method=‘vertofx’ files array No Additional receipt files
Decline Payment Request
Admins can decline requests with a reason:
const response = await fetch (
`https://api.royalti.io/payment-request/ ${ requestId } /decline` ,
{
method: 'PATCH' ,
headers: {
'Authorization' : `Bearer ${ adminToken } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
reason: 'Insufficient balance after recent refund'
})
}
);
const result = await response . json ();
console . log ( 'Status:' , result . status ); // "declined"
response = requests.patch(
f 'https://api.royalti.io/payment-request/ { request_id } /decline' ,
headers = {
'Authorization' : f 'Bearer { admin_token } ' ,
'Content-Type' : 'application/json'
},
json = {
'reason' : 'Insufficient balance after recent refund'
}
)
result = response.json()
print ( f "Status: { result[ 'status' ] } " )
Bulk Delete Payment Requests
Delete multiple payment requests efficiently:
try {
const requestIds = [
'request-id-1' ,
'request-id-2' ,
'request-id-3'
];
const response = await fetch (
'https://api.royalti.io/payment-request/bulk/delete' ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ adminToken } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
ids: requestIds
})
}
);
if ( ! response . ok ) {
throw new Error ( 'Bulk delete failed' );
}
const result = await response . json ();
console . log ( `Deleted ${ result . data . deletedCount } requests` );
console . log ( 'Errors:' , result . data . errors ); // If any failed
} catch ( error ) {
console . error ( 'Bulk delete error:' , error . message );
}
try :
request_ids = [
'request-id-1' ,
'request-id-2' ,
'request-id-3'
]
response = requests.post(
'https://api.royalti.io/payment-request/bulk/delete' ,
headers = {
'Authorization' : f 'Bearer { admin_token } ' ,
'Content-Type' : 'application/json'
},
json = {
'ids' : request_ids
}
)
response.raise_for_status()
result = response.json()
print ( f "Deleted { result[ 'data' ][ 'deletedCount' ] } requests" )
if result[ 'data' ][ 'errors' ]:
print ( f "Errors: { result[ 'data' ][ 'errors' ] } " )
except requests.exceptions.RequestException as e:
print ( f "Bulk delete error: { e } " )
Bulk Delete Notes:
Delete up to 100 requests per operation
Partially successful deletes are allowed (some succeed, some fail)
Check the errors array for failed deletes
Deleted requests cannot be recovered
VertoFX Integration
VertoFX enables international wire transfers to beneficiaries worldwide with competitive FX rates and fast processing times.
Prerequisites
Before using VertoFX for payments, you need:
VertoFX Account - Sign up at verto.co
API Credentials - Obtain your API key from VertoFX dashboard
Workspace Integration - Connect your VertoFX account to Royalti.io
Connect VertoFX Account
Link your VertoFX account to your workspace: const response = await fetch ( 'https://api.royalti.io/integrations/vertofx' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
apiKey: 'your-vertofx-api-key' ,
apiSecret: 'your-vertofx-api-secret' ,
environment: 'production' // or 'sandbox' for testing
})
});
const integration = await response . json ();
console . log ( 'Integration ID:' , integration . data . id );
console . log ( 'Wallets:' , integration . data . wallets );
response = requests.post(
'https://api.royalti.io/integrations/vertofx' ,
headers = {
'Authorization' : f 'Bearer { token } ' ,
'Content-Type' : 'application/json'
},
json = {
'apiKey' : 'your-vertofx-api-key' ,
'apiSecret' : 'your-vertofx-api-secret' ,
'environment' : 'production'
}
)
integration = response.json()
print ( f "Integration ID: { integration[ 'data' ][ 'id' ] } " )
print ( f "Wallets: { integration[ 'data' ][ 'wallets' ] } " )
Create Beneficiary
Add beneficiary bank details for the payment recipient: const response = await fetch (
'https://api.royalti.io/integrations/vertofx/beneficiaries' ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
name: 'John Doe' ,
currency: 'EUR' ,
country: 'DE' ,
bankDetails: {
accountNumber: 'DE89370400440532013000' , // IBAN
bankName: 'Deutsche Bank' ,
swiftCode: 'DEUTDEFF'
}
})
}
);
const beneficiary = await response . json ();
console . log ( 'Beneficiary ID:' , beneficiary . data . id );
Bank details vary by country. The API validates requirements for each region.
Link Beneficiary to User
Associate the VertoFX beneficiary with a Royalti.io user: const response = await fetch (
'https://api.royalti.io/integrations/vertofx/beneficiaries/link' ,
{
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
beneficiaryId: 'vertofx-beneficiary-id' ,
tenantUserId: 'user-uuid' ,
currency: 'EUR'
})
}
);
const link = await response . json ();
console . log ( 'User linked to beneficiary' );
Payment Purpose Codes
VertoFX requires a purpose code for compliance. Available codes:
// Get available purpose codes
const response = await fetch (
'https://api.royalti.io/integrations/vertofx/purpose-codes' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const codes = await response . json ();
Common Codes:
ROYALTY_PAYMENT - Music royalty distributions
INVOICE_PAYMENT - Invoice settlements
SERVICE_FEE - Service charges
COMMISSION - Commission payments
Processing International Payments
Once setup is complete, approve payment requests with the VertoFX method:
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: 'your-vertofx-wallet-id' ,
purposeCode: 'ROYALTY_PAYMENT' ,
currency: 'EUR' ,
amount: 1000.00 , // Amount in EUR
amountUSD: 1090.00 , // USD equivalent
conversionRate: 1.09
})
}
);
const result = await response . json ();
console . log ( 'Transfer initiated:' , result . vertoTransferId );
VertoFX payments are processed asynchronously. The system creates the payment record immediately and updates it with the transfer details once VertoFX confirms the transaction.
Managing Beneficiaries
View and update beneficiary information:
// List all beneficiaries
const response = await fetch (
'https://api.royalti.io/integrations/vertofx/beneficiaries' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const beneficiaries = await response . json ();
// Update beneficiary
const updateResponse = await fetch (
`https://api.royalti.io/integrations/vertofx/beneficiaries/ ${ beneficiaryId } ` ,
{
method: 'PUT' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
bankDetails: {
accountNumber: 'updated-account-number'
}
})
}
);
Automatic Receipt Generation
When you approve a payment request, the system automatically generates a professional PDF receipt and attaches it to the payment record.
How It Works
Approval Triggers Generation
When you call the approve endpoint, receipt generation starts automatically: 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'
})
}
);
const result = await response . json ();
console . log ( 'Receipt Status:' , result . receiptStatus ); // "generated"
Receipt Details
The generated PDF receipt includes:
Payment amount and currency
Recipient information
Transaction date
Payment method details
Unique payment ID for reference
Company branding (if configured)
Access the Receipt
The receipt URL is included in the payment record: const payment = result . payment ;
const receiptUrl = payment . files [ 0 ]; // First file is always the receipt
console . log ( 'Download receipt:' , receiptUrl );
// Example: https://storage.googleapis.com/receipts/payment-123.pdf
The PDF receipt follows this structure:
┌─────────────────────────────────────┐
│ [Your Company Logo] │
│ │
│ PAYMENT RECEIPT │
│ │
│ Receipt #: PMT-2024-03-001 │
│ Date: March 15, 2024 │
│ │
│ PAID TO: │
│ John Doe │
│ [email protected] │
│ │
│ PAYMENT DETAILS: │
│ Amount: $1,500.00 USD │
│ Payment Method: Bank Transfer │
│ Transaction ID: TXN-ABC-123 │
│ │
│ DESCRIPTION: │
│ Q1 2024 Royalty Distribution │
│ │
│ Thank you for your collaboration! │
└─────────────────────────────────────┘
Customization
Receipt templates are configured at the workspace level. Contact support to customize your receipt branding and layout.
Downloading Receipts
Single Receipt:
async function downloadReceipt ( paymentId , token ) {
// Get payment details
const payment = await fetch (
`https://api.royalti.io/payment/ ${ paymentId } ` ,
{
headers: { 'Authorization' : `Bearer ${ token } ` }
}
). then ( r => r . json ());
// Download the receipt
const receiptUrl = payment . files [ 0 ];
const receiptBlob = await fetch ( receiptUrl ). then ( r => r . blob ());
// Save to file
const filename = `receipt- ${ paymentId } .pdf` ;
// (Browser: trigger download, Node: save with fs)
}
Bulk Receipt Download:
async function downloadAllReceipts ( startDate , endDate , token ) {
// Get all payments in date range
const payments = await fetch (
`https://api.royalti.io/payment/?startDate= ${ startDate } &endDate= ${ endDate } ` ,
{
headers: { 'Authorization' : `Bearer ${ token } ` }
}
). then ( r => r . json ());
// Download each receipt
for ( const payment of payments . Payments ) {
if ( payment . files && payment . files . length > 0 ) {
await downloadReceipt ( payment . id , token );
}
}
}
Troubleshooting Receipts
Possible Causes:
Approval request failed before completion
Network timeout during generation
Payment created manually (not from request)
Solution:
Manually upload a receipt using the payment update endpoint:const formData = new FormData ();
formData . append ( 'files' , receiptFile );
await fetch ( `https://api.royalti.io/payment/ ${ paymentId } ` , {
method: 'PUT' ,
headers: { 'Authorization' : `Bearer ${ token } ` },
body: formData
});
Receipt URL not accessible
Cause: Receipt URLs are temporary and expire after 24 hours.Solution: Request a fresh URL by fetching the payment details again:const payment = await fetch (
`https://api.royalti.io/payment/ ${ paymentId } ` ,
{ headers: { 'Authorization' : `Bearer ${ token } ` } }
). then ( r => r . json ());
const freshUrl = payment . files [ 0 ]; // New signed URL
Payment Records
Create Payment
Record a payment transaction:
const response = await fetch ( 'https://api.royalti.io/payment/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
user: userId ,
title: 'Monthly Royalty Payment' ,
transactionDate: new Date (). toISOString (),
currency: 'USD' ,
amount: 1200.00 ,
amountUSD: 1200.00 ,
conversionRate: 1.0 ,
memo: 'March 2024 royalties' ,
paymentRequestId: requestId // Optional: link to payment request
})
});
const payment = await response . json ();
Required Fields:
user - TenantUserId receiving the payment
transactionDate - When the payment was made
currency - Currency code (USD, EUR, GBP, etc.)
amount - Amount in the specified currency
amountUSD - Amount converted to USD
Optional Fields:
title - Description of the payment
memo - Additional notes
conversionRate - Exchange rate used
paymentRequestId - Link to payment request
files - Receipt or proof of payment files
List Payments
Get paginated list of all payments:
const response = await fetch (
'https://api.royalti.io/payment/?page=1&size=20' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const { payments , pagination } = await response . json ();
payments . forEach ( payment => {
console . log ( ` ${ payment . title } : $ ${ payment . amountUSD } to ${ payment . TenantUser . fullName } ` );
});
Query Parameters:
page - Page number
size - Items per page (max: 100)
userId - Filter by recipient
startDate - Filter by date range (YYYY-MM-DD)
endDate - Filter by date range (YYYY-MM-DD)
Get Payment Details
Retrieve specific payment with full details:
const response = await fetch ( `https://api.royalti.io/payment/ ${ paymentId } ` , {
headers: {
'Authorization' : `Bearer ${ token } `
}
});
const payment = await response . json ();
console . log ( 'Recipient:' , payment . TenantUser . fullName );
console . log ( 'Amount:' , payment . amountUSD );
console . log ( 'Date:' , payment . transactionDate );
console . log ( 'Memo:' , payment . memo );
console . log ( 'Files:' , payment . Files ); // Attached receipts
Update Payment
Modify payment details:
const response = await fetch ( `https://api.royalti.io/payment/ ${ paymentId } ` , {
method: 'PUT' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
memo: 'Updated: Paid via bank transfer' ,
title: 'Q1 2024 Royalty Distribution'
})
});
Delete Payment
Remove a payment record:
const response = await fetch ( `https://api.royalti.io/payment/ ${ paymentId } ` , {
method: 'DELETE' ,
headers: {
'Authorization' : `Bearer ${ token } `
}
});
Deleting a payment does NOT automatically adjust user balances. You must manually recalculate accounting after deleting payments.
Bulk Payment Processing
Create Multiple Payments
Process batch payments efficiently:
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: 'USD' ,
amount: 750.00 ,
amountUSD: 750.00
}
];
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 );
Response Format:
{
"status" : "success" ,
"data" : {
"created" : 2 ,
"failed" : 0 ,
"payments" : [
{ "id" : "payment-1" , "user" : "user-id-1" , "amount" : 500.00 },
{ "id" : "payment-2" , "user" : "user-id-2" , "amount" : 750.00 }
],
"errors" : []
}
}
Payment Settings
Payment Settings store bank account details, PayPal information, and other payment methods for each user. These settings are used when creating payment requests and processing payments.
Supported Payment Types
Type Description Use Case BankWireTransferBank account for wire transfers Domestic and international bank payments PayPalPayPal account Quick online payments CardDebit/credit card Card-based payments CryptoCryptocurrency wallet Digital currency payments
Create Payment Settings
Configure bank account details for wire transfers: 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' , // For international
IBAN: 'US64SVBKUS6S3300958879' , // Optional
country: 'United States' ,
countryCode: 'US' ,
currency: 'USD'
},
isDefault: true ,
memo: 'Main account for USD payments'
})
});
const setting = await response . json ();
console . log ( 'Setting ID:' , setting . paymentSetting . id );
response = requests.post(
'https://api.royalti.io/payment-setting/' ,
headers = {
'Authorization' : f 'Bearer { token } ' ,
'Content-Type' : 'application/json'
},
json = {
'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'
}
)
setting = response.json()
print ( f "Setting ID: { setting[ 'paymentSetting' ][ 'id' ] } " )
Required Fields:
bankName - Name of the bank
accountNumber - Bank account number
bankCode - Bank code or sort code
country - Country name
countryCode - ISO country code
currency - Currency code
Configure PayPal account for payments: 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 PayPal' ,
type: 'PayPal' ,
settings: {
paypalEmail: '[email protected] ' ,
country: 'United States' ,
countryCode: 'US' ,
currency: 'USD'
},
isDefault: false ,
memo: 'PayPal account for quick payments'
})
});
const setting = await response . json ();
response = requests.post(
'https://api.royalti.io/payment-setting/' ,
headers = {
'Authorization' : f 'Bearer { token } ' ,
'Content-Type' : 'application/json'
},
json = {
'name' : 'Primary PayPal' ,
'type' : 'PayPal' ,
'settings' : {
'paypalEmail' : '[email protected] ' ,
'country' : 'United States' ,
'countryCode' : 'US' ,
'currency' : 'USD'
},
'isDefault' : False ,
'memo' : 'PayPal account for quick payments'
}
)
Required Fields:
paypalEmail - PayPal email address
country - Country name
countryCode - ISO country code
currency - Currency code
Admins can create payment settings for other users: const response = await fetch ( 'https://api.royalti.io/payment-setting/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ adminToken } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
user: 'target-user-uuid' , // TenantUserId
name: 'User Bank Account' ,
type: 'BankWireTransfer' ,
settings: {
// ... bank details
},
isDefault: true
})
});
When the user parameter is provided, the setting is created for that user instead of the authenticated user.
List Payment Settings
Get all payment settings for filtering and management:
const response = await fetch (
'https://api.royalti.io/payment-setting/?user=${userId}&default=true' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const settings = await response . json ();
settings . forEach ( setting => {
console . log ( ` ${ setting . name } ( ${ setting . type } ): ${ setting . isDefault ? 'Default' : 'Alternative' } ` );
});
Query Parameters:
user - Filter by TenantUserId
default - Filter by isDefault (true/false)
type - Filter by payment type
q - Search by name or memo
page - Page number
size - Items per page
Get Payment Setting Details
Retrieve specific setting:
const response = await fetch (
`https://api.royalti.io/payment-setting/ ${ settingId } ` ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const setting = await response . json ();
console . log ( 'Name:' , setting . name );
console . log ( 'Type:' , setting . type );
console . log ( 'Settings:' , setting . settings );
console . log ( 'Is Default:' , setting . isDefault );
Update Payment Settings
Modify existing payment preferences:
const response = await fetch ( `https://api.royalti.io/payment-setting/ ${ settingId } ` , {
method: 'PUT' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
minimumPayment: 100.00 ,
paymentMethod: 'paypal' ,
paymentDetails: {
email: '[email protected] '
}
})
});
Multi-Currency Handling
Currency Conversion
When paying in non-USD currency, specify conversion rate:
const response = await fetch ( 'https://api.royalti.io/payment/' , {
method: 'POST' ,
headers: {
'Authorization' : `Bearer ${ token } ` ,
'Content-Type' : 'application/json'
},
body: JSON . stringify ({
user: userId ,
title: 'EUR Payment' ,
transactionDate: new Date (). toISOString (),
currency: 'EUR' ,
amount: 1000.00 , // 1000 EUR
amountUSD: 1090.00 , // Converted amount
conversionRate: 1.09 , // EUR to USD rate
memo: 'Payment made in EUR'
})
});
Common Currencies:
USD - US Dollar
EUR - Euro
GBP - British Pound
CAD - Canadian Dollar
AUD - Australian Dollar
NGN - Nigerian Naira
Always provide both the original amount and USD conversion. The system uses amountUSD for accounting calculations.
Payment History & Reports
Payment Summary
Get aggregate payment statistics:
const response = await fetch (
'https://api.royalti.io/payment/summary?startDate=2024-01-01&endDate=2024-12-31' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const summary = await response . json ();
console . log ( 'Total paid:' , summary . totalAmount );
console . log ( 'Number of payments:' , summary . count );
console . log ( 'Unique recipients:' , summary . uniqueUsers );
Monthly Payment Report
Get month-by-month payment breakdown:
const response = await fetch (
'https://api.royalti.io/payment/monthly?year=2024' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const monthlyData = await response . json ();
monthlyData . forEach ( month => {
console . log ( ` ${ month . month } : $ ${ month . totalAmount } ( ${ month . count } payments)` );
});
Export Payment Data
Download payment records as CSV:
const response = await fetch (
'https://api.royalti.io/payment/export/csv?startDate=2024-01-01&endDate=2024-12-31' ,
{
headers: {
'Authorization' : `Bearer ${ token } `
}
}
);
const csvData = await response . text ();
// Save to file or process as needed
Best Practices
Payment Workflow
Regular payment cycles - Establish consistent monthly/quarterly payment schedules
Minimum thresholds - Set reasonable minimum amounts to reduce transaction fees
Batch processing - Group payments by payment method for efficiency
Clear communication - Notify users before and after payment processing
Record Keeping
Attach receipts - Upload proof of payment for all transactions
Detailed memos - Include payment period, method, and any relevant notes
Transaction IDs - Store external payment processor transaction IDs
Currency clarity - Always specify both original and USD amounts
Error Handling
Always verify payment creation and handle errors:
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 ( 'Payment created. User balance updated:' , stats . due );
return payment ;
} catch ( error ) {
console . error ( 'Payment creation failed:' , error . message );
throw error ;
}
}
Reconciliation
Regularly reconcile payments with accounting:
// Get all payments for a period
const paymentsResponse = await fetch (
'https://api.royalti.io/payment/?startDate=2024-03-01&endDate=2024-03-31' ,
{ headers: { 'Authorization' : `Bearer ${ token } ` } }
);
const { payments } = await paymentsResponse . json ();
// Get transaction summary from accounting
const accountingResponse = await fetch (
'https://api.royalti.io/accounting/transactions/summary' ,
{ headers: { 'Authorization' : `Bearer ${ token } ` } }
);
const { summary } = await accountingResponse . json ();
console . log ( 'Payment records total:' , payments . reduce (( sum , p ) => sum + p . amountUSD , 0 ));
console . log ( 'Accounting total paid:' , summary . totalPayments );
Troubleshooting
User balance not updated after payment
Checklist:
Payment was successfully created (check response)
Payment amount is positive
User ID is correct
Payment is linked to correct workspace
Solutions:
Verify payment was created: GET /payment/{id}
Check user stats: GET /accounting/{userId}/stats
Force refresh: GET /accounting/{userId}/stats?forceRefresh=true
If still incorrect, delete and recreate payment
Payment request shows 'pending' but user can't create new request
Cause: Users can only have one pending payment request at a time.Solution:
Check existing request: GET /payment-request/?userId={userId}&status=pending
Complete or decline the existing request
User can then create a new request
Currency conversion not working correctly
Common issues:
Wrong conversion rate direction (e.g., using USD/EUR instead of EUR/USD)
Missing conversionRate field
amountUSD doesn’t match calculation
Solution: // Correct conversion example
const eurAmount = 1000 ;
const eurToUsdRate = 1.09 ; // 1 EUR = 1.09 USD
const usdAmount = eurAmount * eurToUsdRate ; // 1090 USD
{
currency : 'EUR' ,
amount : eurAmount ,
amountUSD : usdAmount ,
conversionRate : eurToUsdRate
}
Bulk payment partially failed
Expected behavior: Bulk operations continue processing even if some fail.Check response: const result = await response . json ();
console . log ( 'Successful:' , result . data . created );
console . log ( 'Failed:' , result . data . failed );
console . log ( 'Errors:' , result . data . errors );
Solution: Review errors array and retry failed payments individually.
Payment request created but approval fails - 'PaymentSettingId is required'
Cause: Payment request was created without a PaymentSettingId, but approval requires one to process the payment.Prevention:
Always provide PaymentSettingId when creating payment requests (unless you plan to update it before approval):// Good: Payment setting provided
const request = await fetch ( 'https://api.royalti.io/payment-request/' , {
method: 'POST' ,
body: JSON . stringify ({
PaymentSettingId: 'setting-uuid' , // Include this
currency: 'USD' ,
amountUSD: 500.00
})
});
// Risky: No payment setting
const request = await fetch ( 'https://api.royalti.io/payment-request/' , {
method: 'POST' ,
body: JSON . stringify ({
currency: 'USD' ,
amountUSD: 500.00
// Missing PaymentSettingId
})
});
If already created without PaymentSettingId:
User must configure payment settings first via Payment Settings API
Create a new payment request with the PaymentSettingId
Or contact support to manually update the request
API Reference
For complete endpoint documentation, see:
Payment Requests:
Payments:
Payment Settings:
Related Guides: