Xero Payments Management
Overview
Payments in Xero record the movement of money against invoices, credit notes, and overpayments. For MSPs, payment tracking is critical for cash flow management -- monitoring which clients have paid their monthly managed services invoices, which are overdue, and reconciling incoming payments against the correct invoices.
Core Concepts
Payment Types
| Type | Description | MSP Use Case |
|---|
| Accounts Receivable Payment | Payment received from a customer | Client paying managed services invoice |
| Accounts Payable Payment | Payment made to a supplier | Paying vendor for software licenses |
| Overpayment | Payment exceeding invoice amount | Client overpayment to be credited |
| Prepayment | Payment before invoice is created | Retainer or deposit from client |
Payment Status
| Status | Description |
|---|
AUTHORISED | Payment recorded and active |
DELETED | Payment has been deleted/reversed |
Payment Flow
Invoice (AUTHORISED) + Payment --> Invoice (PAID)
Invoice (AUTHORISED) + Partial Payment --> Invoice (AUTHORISED, AmountDue reduced)
Invoice (AUTHORISED) + Overpayment --> Invoice (PAID) + Overpayment Credit
Field Reference
Core Fields
| Field | Type | Required | Description |
|---|
PaymentID | string (UUID) | System | Auto-generated unique identifier |
Invoice | object | Yes* | Invoice being paid (InvoiceID or InvoiceNumber) |
Account | object | Yes | Bank account receiving/sending payment (AccountID or Code) |
Date | string | Yes | Payment date (YYYY-MM-DDT00:00:00) |
Amount | decimal | Yes | Payment amount |
CurrencyRate | decimal | No | Exchange rate for multi-currency |
Reference | string | No | Payment reference (e.g., check number, EFT ref) |
IsReconciled | boolean | No | Whether the payment is reconciled |
Status | string | Read-only | AUTHORISED or DELETED |
PaymentType | string | Read-only | ACCRECPAYMENT, ACCPAYPAYMENT, etc. |
*Either Invoice or CreditNote is required.
Related Object Fields
| Field | Type | Description |
|---|
Invoice.InvoiceID | string | UUID of the invoice |
Invoice.InvoiceNumber | string | Invoice number |
Account.AccountID | string | UUID of the bank account |
Account.Code | string | Account code of the bank account |
API Patterns
List Payments
curl -s -X GET "https://api.xero.com/api.xro/2.0/Payments" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Accept: application/json"
With Filters:
# Payments received (AR)
curl -s -X GET "https://api.xero.com/api.xro/2.0/Payments?where=PaymentType==%22ACCRECPAYMENT%22" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Accept: application/json"
# Payments in a date range
curl -s -X GET "https://api.xero.com/api.xro/2.0/Payments?where=Date>=DateTime(2026,3,1)&&Date<=DateTime(2026,3,31)" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Accept: application/json"
# Payments for a specific invoice
curl -s -X GET "https://api.xero.com/api.xro/2.0/Payments?where=Invoice.InvoiceID==guid(%22${INVOICE_ID}%22)" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Accept: application/json"
Get Single Payment
curl -s -X GET "https://api.xero.com/api.xro/2.0/Payments/${PAYMENT_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Accept: application/json"
Record a Payment (AR - Client Pays Invoice)
curl -s -X POST "https://api.xero.com/api.xro/2.0/Payments" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Content-Type: application/json" \
-d '{
"Invoice": {
"InvoiceID": "'${INVOICE_ID}'"
},
"Account": {
"Code": "090"
},
"Date": "2026-03-15T00:00:00",
"Amount": 2500.00,
"Reference": "EFT-2026-0315-ACME"
}'
Record a Payment (AP - Pay Vendor Bill)
curl -s -X POST "https://api.xero.com/api.xro/2.0/Payments" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Content-Type: application/json" \
-d '{
"Invoice": {
"InvoiceID": "'${VENDOR_BILL_ID}'"
},
"Account": {
"Code": "090"
},
"Date": "2026-03-10T00:00:00",
"Amount": 525.00,
"Reference": "CHK-4521"
}'
Record Partial Payment
curl -s -X POST "https://api.xero.com/api.xro/2.0/Payments" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Content-Type: application/json" \
-d '{
"Invoice": {
"InvoiceID": "'${INVOICE_ID}'"
},
"Account": {
"Code": "090"
},
"Date": "2026-03-15T00:00:00",
"Amount": 1000.00,
"Reference": "Partial payment - remainder due by 3/31"
}'
Delete (Reverse) a Payment
curl -s -X POST "https://api.xero.com/api.xro/2.0/Payments/${PAYMENT_ID}" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Content-Type: application/json" \
-d '{
"PaymentID": "'${PAYMENT_ID}'",
"Status": "DELETED"
}'
Batch Create Payments
curl -s -X POST "https://api.xero.com/api.xro/2.0/Payments?summarizeErrors=false" \
-H "Authorization: Bearer ${ACCESS_TOKEN}" \
-H "xero-tenant-id: ${XERO_TENANT_ID}" \
-H "Content-Type: application/json" \
-d '{
"Payments": [
{
"Invoice": { "InvoiceID": "inv-001" },
"Account": { "Code": "090" },
"Date": "2026-03-15T00:00:00",
"Amount": 2500.00,
"Reference": "EFT-ACME"
},
{
"Invoice": { "InvoiceID": "inv-002" },
"Account": { "Code": "090" },
"Date": "2026-03-15T00:00:00",
"Amount": 1800.00,
"Reference": "EFT-TECHSTART"
}
]
}'
Common Workflows
Check Outstanding Balances for All Clients
async function getOutstandingBalances() {
const invoices = await fetchAllInvoices({
where: 'Type=="ACCREC"&&Status=="AUTHORISED"&&AmountDue>0'
});
const balancesByContact = {};
for (const invoice of invoices) {
const contactName = invoice.Contact.Name;
if (!balancesByContact[contactName]) {
balancesByContact[contactName] = {
contactId: invoice.Contact.ContactID,
totalOutstanding: 0,
totalOverdue: 0,
invoices: []
};
}
balancesByContact[contactName].totalOutstanding += invoice.AmountDue;
const dueDate = new Date(invoice.DueDate);
if (dueDate < new Date()) {
balancesByContact[contactName].totalOverdue += invoice.AmountDue;
}
balancesByContact[contactName].invoices.push({
number: invoice.InvoiceNumber,
amount: invoice.AmountDue,
dueDate: invoice.DueDate,
isOverdue: dueDate < new Date()
});
}
return balancesByContact;
}
Payment Aging Report
async function getPaymentAging() {
const invoices = await fetchAllInvoices({
where: 'Type=="ACCREC"&&Status=="AUTHORISED"&&AmountDue>0'
});
const aging = {
current: [], // Not yet due
thirtyDays: [], // 1-30 days overdue
sixtyDays: [], // 31-60 days overdue
ninetyDays: [], // 61-90 days overdue
overNinety: [] // 90+ days overdue
};
const now = new Date();
for (const invoice of invoices) {
const dueDate = new Date(invoice.DueDate);
const daysOverdue = Math.floor((now - dueDate) / (1000 * 60 * 60 * 24));
const entry = {
contact: invoice.Contact.Name,
invoiceNumber: invoice.InvoiceNumber,
amountDue: invoice.AmountDue,
dueDate: invoice.DueDate,
daysOverdue: Math.ma