Xero Adapter
The Xero adapter provides access to accounting data including contacts, invoices, and items with OAuth2 authentication and multi-tenant organization support.
Installation
npm install @openetl/xero
Features
- OAuth2 Authentication - Full OAuth2 flow with automatic token refresh
- Multi-Tenant Support - Work with multiple Xero organizations
- OAuth Helpers - Built-in methods for authorization flow
- Rate Limiting - Automatic handling of Xero API rate limits
- Full CRUD Support - Download and create resources
- Core Endpoints - Contacts, Invoices, Items, and Accounts
Authentication
The Xero adapter uses oauth2 authentication:
const vault = {
'xero-auth': {
id: 'xero-auth',
type: 'oauth2',
credentials: {
client_id: process.env.XERO_CLIENT_ID,
client_secret: process.env.XERO_CLIENT_SECRET,
access_token: process.env.XERO_ACCESS_TOKEN,
refresh_token: process.env.XERO_REFRESH_TOKEN,
},
},
};
OAuth2 Setup
Step 1: Generate Authorization URL
import { XeroAdapter } from '@openetl/xero';
const authUrl = XeroAdapter.helpers.getCode(
'https://yourapp.com/callback', // Your redirect URI
process.env.XERO_CLIENT_ID
);
// Redirect user to authUrl
// User authorizes your app
// Xero redirects back with ?code=xxx
Step 2: Exchange Code for Tokens
const tokens = await XeroAdapter.helpers.getTokens(
'https://yourapp.com/callback',
process.env.XERO_CLIENT_ID,
process.env.XERO_CLIENT_SECRET,
{ code: 'authorization_code_from_callback' }
);
// tokens = {
// access_token: '...',
// refresh_token: '...',
// expires_in: 1800,
// token_type: 'Bearer'
// }
Step 3: Get Tenant ID
After authentication, get connected organizations:
const response = await fetch('https://api.xero.com/connections', {
headers: {
'Authorization': `Bearer ${tokens.access_token}`,
'Content-Type': 'application/json',
},
});
const connections = await response.json();
// [{ tenantId: 'xxx', tenantName: 'My Company', ... }]
const tenantId = connections[0].tenantId;
Quick Start
import { Orchestrator } from 'openetl';
import { xero } from '@openetl/xero';
const vault = {
'xero-auth': {
id: 'xero-auth',
type: 'oauth2',
credentials: {
client_id: process.env.XERO_CLIENT_ID,
client_secret: process.env.XERO_CLIENT_SECRET,
access_token: process.env.XERO_ACCESS_TOKEN,
refresh_token: process.env.XERO_REFRESH_TOKEN,
},
},
};
const etl = Orchestrator(vault, { xero });
const pipeline = {
id: 'export-contacts',
source: {
id: 'contacts-source',
adapter_id: 'xero',
endpoint_id: 'contacts',
credential_id: 'xero-auth',
config: {
tenant_id: 'your-xero-tenant-id',
},
fields: ['ContactID', 'Name', 'EmailAddress', 'FirstName', 'LastName'],
pagination: { type: 'page', itemsPerPage: 100 },
},
};
const result = await etl.runPipeline(pipeline);
console.log(`Exported ${result.data.length} contacts`);
Endpoints
contacts
Retrieve or create contacts.
| Config Property | Required | Description |
|---|---|---|
tenant_id |
Yes | Xero organization ID |
const connector = {
adapter_id: 'xero',
endpoint_id: 'contacts',
credential_id: 'xero-auth',
config: {
tenant_id: 'your-tenant-id',
},
fields: ['ContactID', 'Name', 'EmailAddress', 'FirstName', 'LastName', 'Phones'],
pagination: { type: 'page', itemsPerPage: 100 },
};
Common contact fields:
ContactID- Unique identifierName- Contact/company nameEmailAddress- Primary emailFirstName,LastName- Contact person namePhones- Phone numbers arrayAddresses- Address array
invoices
Retrieve or create invoices.
const connector = {
adapter_id: 'xero',
endpoint_id: 'invoices',
credential_id: 'xero-auth',
config: {
tenant_id: 'your-tenant-id',
},
fields: ['InvoiceID', 'InvoiceNumber', 'Contact', 'Total', 'Status', 'DueDate'],
filters: [
{ field: 'Status', operator: '=', value: 'AUTHORISED' },
],
pagination: { type: 'page', itemsPerPage: 100 },
};
Common invoice fields:
InvoiceID- Unique identifierInvoiceNumber- Invoice numberType- ACCREC (receivable) or ACCPAY (payable)Contact- Associated contactTotal- Total amountStatus- DRAFT, SUBMITTED, AUTHORISED, PAID, VOIDED
Creating invoices:
const invoice = {
Type: 'ACCREC',
Contact: { ContactID: 'contact-uuid' },
LineItems: [
{ Description: 'Consulting', Quantity: 10, UnitAmount: 150.00, AccountCode: '200' }
],
DueDate: '2024-12-31',
Status: 'DRAFT',
};
items
Retrieve or create inventory items.
const connector = {
adapter_id: 'xero',
endpoint_id: 'items',
credential_id: 'xero-auth',
config: { tenant_id: 'your-tenant-id' },
fields: ['ItemID', 'Code', 'Name', 'Description', 'PurchaseDetails', 'SalesDetails'],
};
accounts
Retrieve chart of accounts.
const connector = {
adapter_id: 'xero',
endpoint_id: 'accounts',
credential_id: 'xero-auth',
config: { tenant_id: 'your-tenant-id' },
fields: ['AccountID', 'Code', 'Name', 'Type', 'Status'],
};
Filtering
Xero supports limited filtering:
filters: [
{ field: 'Status', operator: '=', value: 'ACTIVE' },
{ field: 'ContactID', operator: '=', value: 'guid-here' },
]
Note: Not all fields support filtering. Consult the Xero API documentation for supported filter fields.
Complete Example
import { Orchestrator, Pipeline } from 'openetl';
import { xero } from '@openetl/xero';
import { postgresql } from '@openetl/postgresql';
const vault = {
'xero-auth': {
id: 'xero-auth',
type: 'oauth2',
credentials: {
client_id: process.env.XERO_CLIENT_ID,
client_secret: process.env.XERO_CLIENT_SECRET,
access_token: process.env.XERO_ACCESS_TOKEN,
refresh_token: process.env.XERO_REFRESH_TOKEN,
},
},
'warehouse': {
id: 'warehouse',
type: 'basic',
credentials: {
host: 'localhost',
database: 'analytics',
username: 'etl',
password: process.env.DB_PASSWORD,
},
},
};
const etl = Orchestrator(vault, { xero, postgresql });
const pipeline: Pipeline = {
id: 'sync-invoices',
source: {
id: 'invoices-source',
adapter_id: 'xero',
endpoint_id: 'invoices',
credential_id: 'xero-auth',
config: {
tenant_id: process.env.XERO_TENANT_ID,
},
fields: ['InvoiceID', 'InvoiceNumber', 'Total', 'Status', 'DueDate'],
filters: [
{ field: 'Status', operator: '=', value: 'AUTHORISED' },
],
pagination: { type: 'page', itemsPerPage: 100 },
},
target: {
id: 'invoices-target',
adapter_id: 'postgresql',
endpoint_id: 'table_insert',
credential_id: 'warehouse',
config: { schema: 'accounting', table: 'xero_invoices' },
fields: ['invoice_id', 'invoice_number', 'total', 'status', 'due_date'],
},
error_handling: {
max_retries: 3,
retry_interval: 2000,
fail_on_error: true,
},
};
const result = await etl.runPipeline(pipeline);
console.log(`Synced ${result.data.length} invoices`);
Rate Limiting
Xero has strict rate limits. The adapter handles rate limit responses (HTTP 429) with exponential backoff.
error_handling: {
max_retries: 5,
retry_interval: 2000,
fail_on_error: true,
}