Xero API Integration: A Developer’s Tutorial

published on 22 April 2025

Whether you’re looking to build a Xero app or an internal tool serving your custom needs, you will need to integrate with the Xero API. This tutorial is a step-by-step guide for developers (using Node.js) on how to build a Xero API integration from scratch. We’ll cover everything from setting up a Xero developer app and handling OAuth 2.0 authentication, managing tokens, selecting a Xero organization (tenant), and making API calls (like fetching unpaid invoices).

Prerequisites: This tutorial is technical, you will need to have programming experience and knowledge of Node.js.

Contents:
1. Registering and Configuring a Xero Developer App
2. Implementing OAuth 2.0 Authentication (Node.js)
3. Token Management – Refreshing and Storing Tokens Securely
4. Tenant Selection: Handling Xero Organizations (Connections)
5. Making API Calls to Xero (Fetching Unpaid Invoices)
6. Too Many Steps? Streamlining the Integration Process with Deplyr

💡 Pro tip if you’re building an app for internal use: check out Deplyr. Deplyr handles the Xero integration for you (OAuth, token management, etc.) and provides you with a pre-authenticated SDK so you can make calls as needed to the Xero API (see the $xero object in the code snippet below). Deplyr is intended for internal tools/apps, so if your business uses Xero and you want a quick way to create tools for your team Deplyr may be a good fit. See the example code snippet below:

// If you're using Deplyr
// you get a pre-authenticated xero object.
// No hassle building your own integration.

const tenants = await $xero.tenants;
if (!tenants || tenants.length === 0) {
    throw new Error("No Xero tenants found");
}
const activeTenantId = tenants[0].tenantId;

// Get the item from Xero
const result = await $xero.accountingApi.getItem(activeTenantId, itemId);

Still interested in build your own integration? No worries, we've got you covered. Read on for a step-by-step guide.

1. Registering and Configuring a Xero Developer App

Before writing any code, you need to create a Xero Developer App. This gives you the API credentials (Client ID and Secret) required for OAuth2.0. Follow these steps to register your app:

  • Sign Up / Log In: If you don’t have one, create a free Xero user account and log in to the Xero Developer Center.
  • Create a New App: In the My Apps section, click “New App”. Fill in the required details (app name, company or app URL, etc.) and make sure to add a redirect URI. For development, you can use a localhost redirect like http://localhost:3000/callback (it doesn’t need to be publicly accessible).
  • Generate Credentials: After creating the app, Xero will show you a Client ID. Click “Generate a secret” to get your Client Secret (copy it now – it’s shown only once).
  • Demo Company: If you don’t have a Xero organization for testing, enable the Xero demo company (in Xero’s dashboard) for a sandbox environment. The demo company is a sample organization that you can use to test API calls without affecting real data.

Keep the Client ID and Secret handy (store them in a safe place). We’ll use them in our Node.js app for authentication. Also, note the redirect URI you set. Our app’s OAuth flow must use the exact same URI, or Xero will reject the login (you’d get an “Invalid redirect URI” error if they don’t match).

Keep the Client ID and Secret handy (store them in a safe place). We’ll use them in our Node.js app for authentication. Also, note the redirect URI you set. Our app’s OAuth flow must use the exact same URI, or Xero will reject the login (you’d get an “Invalid redirect URI” error if they don’t match).

2. Implementing OAuth 2.0 Authentication (Node.js)

Xero’s API uses OAuth 2.0 for authorization. In a nutshell, your app will redirect the user to Xero’s login/consent page, the user logs in and grants access, and Xero redirects back with a code that your app exchanges for tokens. We’ll use Node.js (with Express and Xero’s official SDK) to handle this:

Install Dependencies: Start a new Node project and install the Xero SDK and any other needed libraries (Express for web server, Dotenv for config):

npm init -y
npm install express xero-node dotenv

Setup Config: In your project, create a .env file to store the Xero credentials and settings:

# .env
XERO_CLIENT_ID=your_client_id_here
XERO_CLIENT_SECRET=your_client_secret_here
XERO_REDIRECT_URI=http://localhost:3000/callback  # must match what you set in Xero
XERO_SCOPES=openid profile email accounting.transactions accounting.settings offline_access

Here we included openid profile email scopes (optional, for user identity info) and some accounting scopes. Notably, we added offline_access which is required to obtain a refresh token for long-term access. You can adjust scopes based on what data you need (Xero has many scopes for accounting, projects, payroll, etc.). Refer to Xero’s documentation for scope definitions if needed.

Initialize the Xero Client: Now, in your Node app (e.g., index.js or an Express route file), set up the Xero SDK client:

require('dotenv').config();
const express = require('express');
const { XeroClient } = require('xero-node');

const app = express();
const port = 3000;

// Initialize Xero client with credentials and scopes
const xero = new XeroClient({
  clientId: process.env.XERO_CLIENT_ID,
  clientSecret: process.env.XERO_CLIENT_SECRET,
  redirectUris: [process.env.XERO_REDIRECT_URI],
  scopes: process.env.XERO_SCOPES.split(' ')
});

This configures the XeroClient with our app credentials, redirect URL, and requested scopes.

OAuth2 Consent URL: To start the OAuth process, your app should redirect the user to Xero’s authorization URL. The Xero SDK can build this for you:

app.get('/connect', async (req, res) => {
  const consentUrl = await xero.buildConsentUrl();  // generates Xero auth URL
  res.redirect(consentUrl);
});

When the user hits the “Connect to Xero” button (linking to /connect), they’ll be sent to Xero to authorize your app. Xero will then ask the user to log in (if not already) and show a consent screen for the scopes your app requested (e.g., access to their accounting data). Upon approval, Xero calls your redirect URI with a special authorization code.

Handle the Callback: Next, set up a route to handle the redirect after the user authorizes the app (this URI must match XERO_REDIRECT_URI exactly):

app.get('/callback', async (req, res) => {
  try {
    // Complete the OAuth flow by exchanging the code for tokens
    await xero.apiCallback(req.url);
    // At this point, the XeroClient has stored the access token and refresh token internally
    const tokenSet = await xero.readTokenSet();
    console.log("Xero OAuth Tokens:", tokenSet);
    res.send("Xero authentication successful! You can close this window.");
  } catch (err) {
    console.error("OAuth callback error:", err);
    res.status(500).send("Xero OAuth failed");
  }
});

The apiCallback method takes the full callback URL (including the code) and handles the token exchange for us. After this, xero.readTokenSet() gives us the current token set (which includes the access token, refresh token, and their expiry info). We might log or inspect it here, but remember to store these tokens securely for future use (more on that in the next section).

At this stage, our app is authenticated! The user has granted access, and we have an access token to act on their behalf, plus a refresh token if offline access was included.

3. Token Management – Refreshing and Storing Tokens Securely

Working with OAuth tokens is critical. Xero’s access tokens are short-lived (they expire after 30 minutes)​, so your app needs to be prepared to refresh them using the refresh token (which is longer-lived). Let’s break down best practices:

  • Access Token: Used on each API call for authorization (via an HTTP header). It expires every 30 minutes, so after that you’ll get unauthorized errors until refreshed.
  • Refresh Token: Provided if you requested the offline_access scope. This token can be used to get a new access token (and a new refresh token). Xero’s refresh tokens last up to 60 days, but each refresh token can only be used once, Xero uses rotating refresh tokens. This means every time you exchange a refresh token for a new access token, you get a new refresh token too, and the old one expires (typically after a short grace period). Also, if you don’t use a refresh token for 30 days, it expires and the user must re-authorize your app.

Storing Tokens: You should persist the token data (access token, refresh token, expiration times, etc.) in a secure storage (database, encrypted file, etc.), associated with the user’s account in your system. This way, the user doesn’t have to log in to Xero every time; your app can use the stored refresh token to renew access in the background.

Refreshing Tokens: The Xero Node SDK makes refreshing easy. You can call xero.refreshToken() to automatically use the stored refresh token and get new tokens. For example:

// Pseudocode: ensure we have a valid token before making API calls
const tokenSet = await xero.readTokenSet();
if (tokenSet.expired()) {
  console.log("Access token expired, refreshing...");
  const newTokenSet = await xero.refreshToken();
  // TODO: save newTokenSet to your storage for persistence
}

Under the hood, this hits Xero’s identity API to swap the refresh token for a fresh token set (new access & refresh token). If the refresh token itself has expired or is invalid (e.g., if 30 days passed without refresh), Xero will reject the request, then you’ll need to send the user through the OAuth flow again to reconnect.

Secure Storage: Never expose these tokens on the client side or in logs. Treat them like passwords. Also, consider encrypting them at rest. If using a backend framework, store in server-side session or database. The Xero SDK does not automatically persist tokens for you, you must capture the token set (via xero.readTokenSet()) after auth and after each refresh, and update your stored copy.

In summary, token management involves checking expiry, refreshing proactively (for example, you might refresh in the background before the 30-minute window lapses), and securing tokens properly. With this in place, your app can maintain continuous access to Xero without user intervention.

4. Tenant Selection: Handling Xero Organizations (Connections)

One unique aspect of Xero’s API is the concept of tenants. A single Xero user can have access to multiple organizations (companies) in Xero. When they authorize your app, they choose which organization(s) to grant access to. In OAuth 1.0a (Xero’s old API) the token was tied to a single org, but now in OAuth 2.0 an access token is tied to the user and can be used for any of their authorized orgs.

After a successful OAuth flow, your app must determine which Xero tenant (organization) to use for API calls. You cannot call most Xero API endpoints with just the access token alone, you also need a Tenant ID to include in each request. In fact, right after auth, you should call the connections endpoint to get the list of tenants the user authorized.

Get Connections (Tenant IDs): The connections endpoint is a simple GET request to https://api.xero.com/connections with your access token. It returns an array of connections, each containing a tenantId, tenantName, tenantType (e.g., ORGANISATION), and an updatedDate. Using the Xero SDK, you can retrieve this via the updateTenants() helper:

const tenants = await xero.updateTenants();
console.log("Authorized tenants:", tenants);

This populates xero.tenants and returns an array. If the user authorized one company (most common case), there will be just one tenant in the list. If multiple, you may need to let the user pick which organization to work with in your app’s UI.

For example, if you just want the first (or only) tenant’s ID:

const tenantId = tenants[0].tenantId;  // use the first tenant's ID

Keep this tenantId around; it represents a specific Xero organisation. All subsequent API calls to Xero must include this ID, usually via an HTTP header called Xero-Tenant-Id in REST calls. The Xero Node SDK methods typically require you to pass the tenantId as the first argument for any API operation.

Behind the scenes: The tenant ID is essentially the Xero organisation identifier that your token is linked to. The reason Xero does this is to allow a single integration to access multiple organizations (if the user permits). So think of tenantId + access token as your complete credentials for an API call. If you forget the tenant, you’ll get an error or no data because Xero won’t know which organization’s data you’re asking for.

5. Making API Calls to Xero (Fetching Unpaid Invoices)

Now for the fun part: using the Xero API to fetch data. With a valid access token and a tenant ID, you can call any of Xero’s API endpoints that your scopes permit. Xero’s APIs include accounting (invoices, contacts, etc.), assets, projects, and more. As an example, let’s fetch unpaid invoices from the accounting API.

Using the Xero Node SDK, we can call the accountingApi.getInvoices method. This returns a list of invoices for the specified tenant. We can then filter or query for the unpaid ones. In Xero, an invoice that is approved and awaiting payment typically has a Status of AUTHORISED (and it will have an AmountDue > 0). We can filter by status in the request or just retrieve and filter in code. Here’s how to do it:

// Assuming xero.accountingApi is available after authentication and we have tenantId
const result = await xero.accountingApi.getInvoices(tenantId);
// The SDK returns a response object; the invoices are inside .body or .records depending on version
const invoices = result.body.Invoices || result.body.invoices || result.body; 
// (Adjust based on actual SDK response structure)
const unpaidInvoices = invoices.filter(inv => inv.Status === 'AUTHORISED');
console.log(`Found ${unpaidInvoices.length} unpaid invoices in Xero.`);
unpaidInvoices.forEach(inv => {
  console.log(`${inv.InvoiceNumber}: Due ${inv.AmountDue} ${inv.CurrencyCode} by ${inv.DueDate}`);
});

The above code retrieves all invoices and filters those with Status == 'AUTHORISED'. You could also check inv.AmountDue > 0 to catch any partially paid invoices. If performance is a concern (e.g., thousands of invoices), Xero’s API supports a “where” query parameter to filter server-side (the SDK might let you pass a filter string). For instance, you could query getInvoices(tenantId, { where: 'Status=="AUTHORISED"' }) to have Xero return only “AUTHORISED” invoices. Check the Xero API docs for the exact syntax and capabilities of queries.

Using REST calls (Axios) directly: While the SDK simplifies things, you can also call the API with a standard HTTP client. For example, using axios:

const axios = require('axios');
const response = await axios.get('https://api.xero.com/api.xro/2.0/Invoices', {
  headers: {
    Authorization: `Bearer ${accessToken}`,
    'Xero-Tenant-Id': tenantId,
    Accept: 'application/json'
  }
});
const invoices = response.data.Invoices;

This hits the same endpoint directly. Notice we must include the Authorization header with the Bearer token and the Xero-Tenant-Id header with the tenantId. The base URL for Xero’s accounting API is https://api.xero.com/api.xro/2.0/, and we append the endpoint (Invoices). The response JSON contains an array of Invoices. You can filter those similarly. Using the SDK or raw HTTP is up to you – the SDK handles some boilerplate (like constructing URLs, etc.) and provides TypeScript definitions, whereas direct HTTP calls give you more control or may be more familiar in simple cases.

Either way, at this point your backend service can successfully pull data from Xero. You could extend this to other endpoints: e.g., fetching Contacts, Accounts, or creating new invoices or payments. Be mindful of rate limits (Xero allows a certain number of calls per minute/day), and implement error handling. For example, handle 401 Unauthorized (token expired, you need to then refresh and retry) and 429 Too Many Requests (rate limit hit, you need to back off accordingly).

Congratulations, you’ve built a basic Xero API integration in Node.js, complete with OAuth 2.0 authentication, secure token handling, tenant selection, and data retrieval. We went through registering a Xero developer app, implementing the OAuth flow step by step, and making API calls to fetch real accounting data. Along the way, you learned about important concepts like access/refresh tokens and Xero tenant IDs, which are crucial for any Xero integration.

6. Too Many Steps? Streamlining the Integration Process with Deplyr

Building an integration from scratch involves a lot of moving parts including OAuth flows, token management, API calls, and UI development. If you’re developing an internal-facing app or tool, manually implementing the full authentication flow might not be worth the overhead. Deplyr simplifies Xero integrations by handling much of the heavy lifting out of the box. It provides pre-built authentication modules, so you don’t need to reinvent the wheel just to get users authenticated. With Deplyr, you can focus more on your app’s core business logic and less on boilerplate infrastructure.

To learn more about how Deplyr can make this process easy for you, check out the video linked below.

Read more

Built on Unicorn Platform