ンデサヘニニスユヘデ・ヾフポヤヒブムワヹピポフッミヤプァ・ヾ
ヂシトヸウセョラゼヵゥリヒンポセギゼウドネノワユフロヺハィフ
ノホヒソアガイラーメモロヲルゼコゲヒヶウビケラズジレァョヷケ
ダカグフギヵプアズルピハグヷラモバゴバイヂ・ヿボドヶツヺエン
モルモラ・ヹウヴズゼヒソナホルピヨバゾロベパォヶラヌヒブメヅ
レロヅヷリメブゴベンベニシシェゲムゥハゲラミゴヽマ゠ブュロヷ
ヮメェヰネャチニゥポニッィゾイテヲキフヱセケエズダヲータ・ャ
ツヰギンヿャヰゲセハフヸヅュィキァデー・モフヅエヤュバゾイニ
バダヮヿヺロデハモグイアベゥヮフツヸヵマエァヱボヷゥゼザヱッ
ノザタテキコヺヷヰビキマギガゥクカーマザヽソブロゥヰフ・セヷ
デヨテヨ・ィコサルヨレケァハヮモヰハッダゴニゲルボヷホリオゲ
ォヌナベトヂヷビ゠ピェボンエザデカヌボダリドュヿクヅヵシィッ
スバポダサヱ・ラモデミヰョプ゠パワギモテヘハトナリヲャジォコ
ホヅギンレヷョヿフィボギユバヶタボォラゲプタォヅソグュユモゲ
ノヴジドムュオヴヷッノャフテベパハヷポザテミォドヷプャヶ゠ト
ヰホヾヾバズヾグポュケパキョヤゥホベユツヹゴヱヿフセ゠ガヿユ
ヅュヿピプデヿヨギッノッヂズソナポーザグヴテォガルマ゠パゥィ
ザヤデワカイニヺョゾヱビヌハジデヸグネキレバヸヺサゴピデデイ
ヮゴレアヴレエネヶヘヱヵツヘゲヷヂドドエヂチフヘヽロビポピボ
メアミオネハグポスャザパパ゠ネヺツヺトドレボオドンビイトペチ
CODE

Xero API Integration: Automating BPO Financial Operations

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. 1.Application redirects user to Xero authorization
  2. 2.User grants permission
  3. 3.Xero returns authorization code
  4. 4.Application exchanges code for access and refresh tokens
  5. 5.Access tokens expire after 30 minutes
  6. 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(): Promise { const credentials = await loadCredentials('credentials/xero-api.json'); // Check if current token is still valid (with 1-minute buffer) if (Date.now() < credentials.expiresAt - 60000) { return credentials.accessToken; } // Refresh required const response = await fetch('https://identity.xero.com/connect/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': Basic ${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): Promise { const token = await getValidAccessToken(); const credentials = await loadCredentials('credentials/xero-api.json'); const params = new URLSearchParams({ page: '1', pageSize: '100' }); if (status) { params.set('where', Status=="${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 { const invoices = await getInvoices('AUTHORISED'); const today = new Date(); const overdueInvoices = invoices.filter(inv => { const dueDate = new Date(inv.DueDate); return dueDate < today && inv.AmountDue > 0; }); // Group by contact const byContact: Record = {}; for (const inv of overdueInvoices) { const contactName = inv.Contact.Name; if (!byContact[contactName]) { byContact[contactName] = { contactName, totalOverdue: 0, invoices: [] }; } const dueDate = new Date(inv.DueDate); const daysOverdue = Math.floor((today.getTime() - dueDate.getTime()) / 86400000); byContact[contactName].totalOverdue += inv.AmountDue; byContact[contactName].invoices.push({ invoiceNumber: inv.InvoiceNumber, dueDate, amount: inv.AmountDue, daysOverdue }); } return Object.values(byContact) .sort((a, b) => b.totalOverdue - a.totalOverdue); } `

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 ): Promise { const token = await getValidAccessToken(); const credentials = await loadCredentials('credentials/xero-api.json'); const invoice = { Type: 'ACCREC', Contact: { ContactID: contactId }, DueDate: formatDateForXero(dueDate), LineItems: lineItems.map(item => ({ Description: item.description, Quantity: item.quantity, UnitAmount: item.unitAmount, AccountCode: item.accountCode })), Reference: reference, Status: 'AUTHORISED' }; const response = await fetch('https://api.xero.com/api.xro/2.0/Invoices', { method: 'POST', headers: { 'Authorization': Bearer ${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 { const arrears = await getArrearsReport(); if (arrears.length === 0) { console.log('No overdue invoices'); return; } // Generate summary const summary = arrears .map(a => ${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 { const activeAgreements = await getActiveServiceAgreements(); for (const agreement of activeAgreements) { const lineItems = agreement.staff.map(staff => ({ description: ${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( operation: () => Promise, maxRetries: number = 3 ): Promise { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { if (error.status === 401) { // Token expired, refresh and retry await refreshXeroToken(); continue; } if (error.status === 429) { // Rate limited, exponential backoff await sleep(Math.pow(2, attempt) * 1000); continue; } if (attempt === maxRetries) { throw error; } } } throw new Error('Max retries exceeded'); } `

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 { try { const token = await getValidAccessToken(); const org = await getOrganization(); return org.Name === 'ShoreAgents Inc.'; } catch (error) { await sendAlert('Xero integration health check failed', error.message); return false; } } `

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.

xeroapiaccountingfinanceintegrationinvoicingoauth
STEPTEN™

I built an army of AI agents. This is their story — and the tools to build your own. No products to sell. Just a founder sharing the journey.

CONNECT

© 2025-2026 STEPTEN™ · Part of the ShoreAgents ecosystem

Built with Next.js · Supabase · AI Agents · From Clark Freeport Zone, Philippines 🇵🇭