Xero Node.js SDK (OAuth 2.0 Client)
The xero-node SDK provides a comprehensive client for interacting with the Xero APIs (Accounting, Assets, Bankfeeds, Files, Projects, Payroll AU/NZ/UK) using Node.js. It facilitates OAuth 2.0 authentication and API requests, simplifying integration for developers building applications that connect with Xero accounting data. The library is currently in its 15.0.0 major version, with frequent updates (multiple minor/patch releases per month, and major releases roughly every few months) to reflect changes in the underlying Xero API and add new features. Key differentiators include full API coverage across multiple Xero API sets and robust TypeScript support, making it suitable for enterprise-grade integrations requiring strong typing and reliability.
Common errors
-
Error: 401 Unauthorized
cause The access token used for the API request is either missing, invalid, or expired.fixEnsure you have completed the OAuth 2.0 authentication flow correctly, retrieved a valid access token, and are setting it on the `XeroClient`. Implement token refresh logic to get a new access token using your refresh token before making requests with an expired token. -
Error: 400 Bad Request - 'The Contacts field is required.' (or similar for other entities)
cause You are attempting to create or update an entity (e.g., Contacts, Invoices) but the request body is missing or malformed, or required fields within the entity object are not provided.fixVerify that your request body adheres to the Xero API schema for the specific endpoint. Ensure all required fields for the entity type are present and correctly formatted. For example, when creating a contact, `contacts: [{ name: '...' }]` is expected. -
Error: Argument of type '{ name: string; }' is not assignable to parameter of type 'Contact'.cause This TypeScript error occurs when you pass a plain JavaScript object that doesn't fully conform to the `Contact` (or other model) interface expected by the SDK method.fixExplicitly cast your object to the correct type (e.g., `const newContact: Contact = { name: 'Test Contact' };`) or ensure your object strictly matches the model's interface, including all required properties as defined in `xero-node/dist/gen/model/accounting/contact.d.ts`.
Warnings
- breaking Version 13.0.0 introduced breaking changes for Payroll NZ and UK API calls. Specifically, several fields in the `Employee` and `Employment` models (e.g., `firstName`, `lastName`, `dateOfBirth`, `startDate`, `payrollCalendarID`) became required. Calls made with older data models will fail.
- deprecated As of version 13.1.0, several Accounting API endpoints related to `EmployeesAsync` have been marked obsolete and will be removed in future releases. These include `CreateEmployeesAsync`, `GetEmployeeAsync`, `GetEmployeesAsync`, and `UpdateOrCreateEmployeesAsync` variants.
- breaking Version 12.0.0 introduced a breaking change by adding a new required query parameter, `direction`, to the `getFiles` endpoint. Calls to `getFiles` without this parameter will no longer work.
- gotcha Xero's OAuth 2.0 flow requires careful management of access tokens and refresh tokens. Access tokens are short-lived (30 minutes) and refresh tokens are valid for 60 days. Failing to refresh an expired access token or properly store/retrieve tokens will lead to authentication failures.
- gotcha The Xero API expects a `tenantId` (also known as `Xero-Tenant-Id` header) for most operations after authentication. This ID identifies the specific Xero organization you are interacting with. Forgetting to pass it or using an incorrect ID will result in errors.
Install
-
npm install xero-node -
yarn add xero-node -
pnpm add xero-node
Imports
- XeroClient
const XeroClient = require('xero-node');import { XeroClient } from 'xero-node'; - AccountingApi
import { Accounting } from 'xero-node';import { AccountingApi } from 'xero-node'; - Contact
import { Contact } from 'xero-node';import { Contact } from 'xero-node/dist/gen/model/accounting/contact'; - IXeroClientConfig
import { XeroClientConfig } from 'xero-node';import { IXeroClientConfig } from 'xero-node';
Quickstart
import { XeroClient, Contact, Contacts } from 'xero-node';
import { Response } from 'node-fetch';
const client_id = process.env.XERO_CLIENT_ID ?? '';
const client_secret = process.env.XERO_CLIENT_SECRET ?? '';
const redirect_uri = process.env.XERO_REDIRECT_URI ?? 'http://localhost:3000/callback';
const scopes = ['accounting.contacts.read', 'offline_access'];
const xero = new XeroClient({
clientId: client_id,
clientSecret: client_secret,
redirectUris: [redirect_uri],
scopes: scopes,
});
async function authenticateAndFetchContacts() {
if (!client_id || !client_secret) {
console.error('XERO_CLIENT_ID and XERO_CLIENT_SECRET environment variables must be set.');
return;
}
// In a real application, you would store and retrieve tokens securely.
// This is a simplified example to get a token via a mock code (requires a valid initial auth flow).
// For a real-world scenario, you'd perform the OAuth2 dance.
// For demonstration purposes, we assume a refresh token is available or use a pre-authorized token.
// Example of setting a token from a previous authorization (replace with actual token management)
xero.setTokenSet({
access_token: process.env.XERO_ACCESS_TOKEN ?? 'YOUR_INITIAL_ACCESS_TOKEN',
refresh_token: process.env.XERO_REFRESH_TOKEN ?? 'YOUR_INITIAL_REFRESH_TOKEN',
expires_at: Date.now() / 1000 + 3600 // Example: expires in 1 hour
});
try {
// Ensure token is fresh
if (await xero.apiClient.checkTokenSet() && xero.apiClient.tokenSet.expired()) {
console.log('Access token expired, attempting to refresh...');
await xero.apiClient.refreshToken();
console.log('Token refreshed successfully.');
}
console.log('Fetching contacts...');
const contactsResponse = await xero.accountingApi.getContacts(xero.tenantIds[0]);
const contacts = contactsResponse.body.contacts;
if (contacts && contacts.length > 0) {
console.log(`Found ${contacts.length} contacts. First contact: ${contacts[0].name}`);
} else {
console.log('No contacts found.');
}
} catch (error) {
console.error('Error fetching contacts:', error);
if (error instanceof Response) {
const errorBody = await error.text();
console.error('API Error Response:', errorBody);
}
}
}
authenticateAndFetchContacts();