Skip to main content

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.
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'
}

Quick Start: Payment Workflow

1

Check Who's Owed Payment

Get list of users with outstanding balances.
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();

// Filter users meeting minimum threshold
const eligible = Users.filter(user => user.Due >= 50);
Python
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]
2

Create Payment Record

Record a payment transaction with details and optional receipt upload.
Node.js
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);
Python
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']}")
3

Verify Accounting Updated

Check that the user’s 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('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.
Node.js
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);
}
Python
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: pendingapproved 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:
Node.js
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);
}
Python
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:
Node.js
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:
  1. Creates a Payment record
  2. Generates a PDF receipt
  3. Uploads the receipt to cloud storage
  4. Updates the request status to “approved”
  5. Optionally processes international payments via VertoFX
Basic approval creates a payment record with automatic receipt generation:
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',
      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
Python
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.
Approval Parameters:
ParameterTypeRequiredDescription
titlestringNoPayment title (defaults to request title)
transactionDatestringNoTransaction date (defaults to current date)
currencystringNoCurrency code (defaults to request currency)
amountnumberNoPayment amount (defaults to request amount)
amountUSDnumberNoUSD amount (defaults to request amountUSD)
conversionRatenumberNoExchange rate
memostringNoAdditional notes
methodstringNoPayment method (‘vertofx’ for international)
walletIdstringConditionalRequired if method=‘vertofx’
purposeCodestringConditionalRequired if method=‘vertofx’
filesarrayNoAdditional receipt files

Decline Payment Request

Admins can decline requests with a reason:
Node.js
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"
Python
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:
Node.js
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);
}
Python
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:
  1. VertoFX Account - Sign up at verto.co
  2. API Credentials - Obtain your API key from VertoFX dashboard
  3. Workspace Integration - Connect your VertoFX account to Royalti.io
1

Connect VertoFX Account

Link your VertoFX account to your workspace:
Node.js
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);
Python
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']}")
2

Create Beneficiary

Add beneficiary bank details for the payment recipient:
Node.js
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.
3

Link Beneficiary to User

Associate the VertoFX beneficiary with a Royalti.io user:
Node.js
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:
Node.js
// 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:
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: '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:
Node.js
// 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

1

Approval Triggers Generation

When you call the approve endpoint, receipt generation starts automatically:
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'
    })
  }
);

const result = await response.json();
console.log('Receipt Status:', result.receiptStatus);  // "generated"
2

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)
3

Access the Receipt

The receipt URL is included in the payment record:
Node.js
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

Receipt Format

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:
Node.js
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:
Node.js
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
});
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:
Node.js
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:
Node.js
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:
Node.js
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:
Node.js
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:
Node.js
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:
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: '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

TypeDescriptionUse Case
BankWireTransferBank account for wire transfersDomestic and international bank payments
PayPalPayPal accountQuick online payments
CardDebit/credit cardCard-based payments
CryptoCryptocurrency walletDigital currency payments

Create Payment Settings

Configure bank account details for wire transfers:
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',  // 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);
Python
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

List Payment Settings

Get all payment settings for filtering and management:
Node.js
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:
Node.js
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:
Node.js
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:
Node.js
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:
Node.js
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:
Node.js
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:
Node.js
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

  1. Regular payment cycles - Establish consistent monthly/quarterly payment schedules
  2. Minimum thresholds - Set reasonable minimum amounts to reduce transaction fees
  3. Batch processing - Group payments by payment method for efficiency
  4. Clear communication - Notify users before and after payment processing

Record Keeping

  1. Attach receipts - Upload proof of payment for all transactions
  2. Detailed memos - Include payment period, method, and any relevant notes
  3. Transaction IDs - Store external payment processor transaction IDs
  4. Currency clarity - Always specify both original and USD amounts

Error Handling

Always verify payment creation and handle errors:
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('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:
Node.js
// 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

Checklist:
  • Payment was successfully created (check response)
  • Payment amount is positive
  • User ID is correct
  • Payment is linked to correct workspace
Solutions:
  1. Verify payment was created: GET /payment/{id}
  2. Check user stats: GET /accounting/{userId}/stats
  3. Force refresh: GET /accounting/{userId}/stats?forceRefresh=true
  4. If still incorrect, delete and recreate payment
Cause: Users can only have one pending payment request at a time.Solution:
  1. Check existing request: GET /payment-request/?userId={userId}&status=pending
  2. Complete or decline the existing request
  3. User can then create a new request
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
}
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.
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:
  1. User must configure payment settings first via Payment Settings API
  2. Create a new payment request with the PaymentSettingId
  3. Or contact support to manually update the request

API Reference

For complete endpoint documentation, see: Payment Requests: Payments: Payment Settings: Related Guides: