Financial operations in a BPO company involve recurring activities: monthly invoice generation, payment tracking, arrears monitoring, and reporting. Manual execution of these tasks introduces errors and consumes staff time that could be better allocated.
Xero provides a comprehensive API for accounting operations. This document covers the integration architecture, key implementation patterns, and operational considerations for automating BPO financial workflows.
Integration Overview
OAuth2 Authentication
Xero uses OAuth2 with the authorization code flow:
- 1.Application redirects user to Xero authorization
- 2.User grants permission
- 3.Xero returns authorization code
- 4.Application exchanges code for access and refresh tokens
- 5.Access tokens expire after 30 minutes
- 6.Refresh tokens enable obtaining new access tokens
`typescript
interface XeroTokens {
accessToken: string;
refreshToken: string;
expiresAt: number;
tenantId: string;
}
`
Tenant Identification
Xero's multi-tenant architecture requires a tenant ID header on every API call:
`typescript
const headers = {
'Authorization': Bearer ${accessToken},
'xero-tenant-id': '44f7efa2-60b0-4623-af05-ae2eda812081',
'Accept': 'application/json'
};
`
Without this header, requests fail with 403 errors.
Token Management
Automatic Refresh
Access tokens expire frequently. The integration must handle refresh automatically:
`typescript
async function getValidAccessToken(): PromiseBasic ${Buffer.from(${CLIENT_ID}:${CLIENT_SECRET}).toString('base64')}
},
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: credentials.refreshToken
})
});
const tokens = await response.json();
const newCredentials: XeroTokens = {
accessToken: tokens.access_token,
refreshToken: tokens.refresh_token,
expiresAt: Date.now() + (tokens.expires_in * 1000),
tenantId: credentials.tenantId
};
await saveCredentials('credentials/xero-api.json', newCredentials);
return newCredentials.accessToken;
}
`
Refresh Token Expiration
Refresh tokens remain valid as long as they're used within 60 days. Regular API activity (such as daily arrears checks) maintains token validity. If a refresh token expires, re-authorization through the OAuth flow is required.
Core Operations
Invoice Retrieval
`typescript
interface Invoice {
InvoiceID: string;
InvoiceNumber: string;
Contact: { Name: string; ContactID: string };
DueDate: string;
AmountDue: number;
Status: string;
}
async function getInvoices(status?: string): PromiseStatus=="${status}");
}
const response = await fetch(
https://api.xero.com/api.xro/2.0/Invoices?${params},
{
headers: {
'Authorization': Bearer ${token},
'xero-tenant-id': credentials.tenantId,
'Accept': 'application/json'
}
}
);
const data = await response.json();
return data.Invoices;
}
`
Arrears Reporting
Identifying overdue invoices is critical for cash flow management:
`typescript
interface ArrearsEntry {
contactName: string;
totalOverdue: number;
invoices: Array<{
invoiceNumber: string;
dueDate: Date;
amount: number;
daysOverdue: number;
}>;
}
async function getArrearsReport(): Promise`
Invoice Creation
Automated invoice generation based on active service agreements:
`typescript
interface InvoiceLineItem {
description: string;
quantity: number;
unitAmount: number;
accountCode: string;
}
async function createInvoice(
contactId: string,
lineItems: InvoiceLineItem[],
dueDate: Date,
reference: string
): PromiseBearer ${token},
'xero-tenant-id': credentials.tenantId,
'Content-Type': 'application/json'
},
body: JSON.stringify({ Invoices: [invoice] })
});
const data = await response.json();
return data.Invoices[0].InvoiceID;
}
`
Automated Workflows
Daily Arrears Check
Runs at 7 AM Philippines time:
`typescript
async function dailyArrearsCheck(): Promise${a.contactName}: $${a.totalOverdue.toFixed(2)} (${a.invoices.length} invoices))
.join('\n');
// Send to finance channel
await sendNotification('finance', Daily Arrears Report\n\n${summary});
// Flag critical cases (>30 days or >$5000)
const critical = arrears.filter(a =>
a.totalOverdue > 5000 ||
a.invoices.some(i => i.daysOverdue > 30)
);
if (critical.length > 0) {
await sendNotification('urgent',
Critical arrears require attention: ${critical.map(c => c.contactName).join(', ')}
);
}
}
`
Monthly Invoice Generation
Runs on the 1st and 21st of each month:
`typescript
async function generateMonthlyInvoices(): Promise${staff.name} - ${staff.role} (Monthly Service),
quantity: 1,
unitAmount: staff.monthlyRate,
accountCode: '200' // Sales revenue
}));
const dueDate = addDays(new Date(), 7);
const reference = SA-${agreement.clientCode}-${formatMonth(new Date())};
const invoiceId = await createInvoice(
agreement.xeroContactId,
lineItems,
dueDate,
reference
);
console.log(Created invoice ${invoiceId} for ${agreement.clientName});
}
}
`
Error Handling
Retry Logic
Network issues and rate limits require resilient handling:
`typescript
async function xeroApiCall`
Validation
Verify invoice data before submission:
`typescript
function validateInvoice(invoice: InvoiceInput): string[] {
const errors: string[] = [];
if (!invoice.contactId) {
errors.push('Contact ID is required');
}
if (!invoice.lineItems || invoice.lineItems.length === 0) {
errors.push('At least one line item is required');
}
for (const item of invoice.lineItems) {
if (item.unitAmount <= 0) {
errors.push(Invalid unit amount: ${item.unitAmount});
}
}
return errors;
}
`
Philippine-Specific Considerations
BIR Compliance
Bureau of Internal Revenue requirements affect invoice formatting. Xero's default templates may not include all required fields. Custom PDF templates should include:
- Taxpayer Identification Number (TIN)
- Registered address in required format
- Official Receipt numbers where applicable
Withholding Tax
Certain payments require withholding tax deductions:
`typescript
const WITHHOLDING_TAX_RATE = 0.02; // 2% Expanded Withholding Tax
function calculateNetReceivable(invoiceAmount: number): {
gross: number;
withholdingTax: number;
netReceivable: number;
} {
const withholdingTax = invoiceAmount * WITHHOLDING_TAX_RATE;
return {
gross: invoiceAmount,
withholdingTax,
netReceivable: invoiceAmount - withholdingTax
};
}
`
Multi-Currency
Most ShoreAgents clients pay in USD, but some Philippine operations are in PHP. Xero supports multi-currency accounting:
`typescript
const invoice = {
Type: 'ACCREC',
Contact: { ContactID: contactId },
CurrencyCode: 'USD', // Invoice currency
// Line amounts in invoice currency
LineItems: [...]
};
`
Monitoring and Alerting
API Health Check
Daily verification that the integration is functioning:
`typescript
async function xeroHealthCheck(): Promise`
Rate Limit Monitoring
Xero has API rate limits. Track usage to avoid hitting limits:
`typescript
let apiCallCount = 0;
const RATE_LIMIT_WINDOW = 60000; // 1 minute
const MAX_CALLS_PER_MINUTE = 60;
function trackApiCall(): void {
apiCallCount++;
if (apiCallCount > MAX_CALLS_PER_MINUTE * 0.8) {
console.warn(Approaching rate limit: ${apiCallCount} calls in current window);
}
setTimeout(() => apiCallCount--, RATE_LIMIT_WINDOW);
}
`
FAQ
Why Xero instead of other accounting software?
ShoreAgents' existing accountant used Xero before the automation project began. Migrating accounting systems involves significant effort—chart of accounts mapping, historical data migration, staff training. The integration built on existing infrastructure rather than requiring a platform change.
How reliable is the OAuth token refresh?
Over 18 months of operation, token refresh has failed twice due to network issues. Both times, the retry logic recovered automatically. The daily arrears check ensures regular API activity, preventing refresh token expiration.
What happens if the API changes?
Xero provides versioned APIs and deprecation notices. The integration uses stable v2.0 endpoints. Breaking changes are communicated months in advance. Monitoring includes checking for deprecation warnings in API responses.
Can this handle multiple Xero organizations?
Yes. Each organization has a unique tenant ID. The integration can manage multiple organizations by maintaining separate credential sets and specifying the appropriate tenant ID for each operation.
How is data synchronized with the operational database?
Invoice creation records are stored locally with the Xero invoice ID. This enables correlation between service agreements and financial records. Payment status updates could be synchronized via webhooks, though the current implementation polls daily.
Financial automation reduces operational overhead and error rates. The initial integration investment is recovered through consistent, timely invoicing and proactive arrears management.
