# How to Integrate Retool with Xero

- Tool: Retool
- Difficulty: Intermediate
- Time required: 30 minutes
- Last updated: April 2026

## TL;DR

Connect Retool to Xero using a REST API Resource with OAuth 2.0. Configure Xero's API credentials in Retool's Resources tab, handle the tenant ID requirement for multi-organisation access, and build financial dashboards showing invoices, bills, bank transactions, and contacts. Xero's REST API covers international accounting workflows for UK, Australian, and New Zealand businesses.

## Build International Accounting Dashboards with Xero and Retool

Xero is the dominant accounting platform in the UK, Australia, and New Zealand, with strong adoption across other international markets. Finance teams using Xero often need operational dashboards that surface invoice status, accounts receivable aging, bank transaction summaries, and contact payment histories — information that Xero's built-in reports provide but that can't be easily customised or combined with data from other systems. Retool bridges this gap by connecting to Xero's Accounting API and presenting financial data in fully customisable dashboards.

Xero's REST API covers the complete accounting data model: invoices (both accounts receivable and accounts payable), contacts (customers and suppliers), bank transactions, payments, credit notes, purchase orders, expense claims, payroll, and financial statements. Every API call must include a Xero-tenant-id header identifying which Xero organisation to query — this is Xero's mechanism for multi-organisation support, where a single API credential can access multiple client organisations. In Retool, you configure this header once in the Resource settings or pass it dynamically from a query parameter.

Building a Retool dashboard on top of Xero gives finance teams capabilities that the Xero interface doesn't natively provide: cross-filtering invoices by custom criteria, joining Xero contacts with your CRM data, building approval workflows for bills before they're paid, and automating repetitive data entry tasks through Retool forms that write back to Xero's API. The integration is particularly powerful for accounting firms managing multiple Xero client organisations from a single Retool instance.

## Before you start

- A Xero account with at least one connected organisation
- A Xero Developer application created at developer.xero.com with OAuth 2.0 credentials (Client ID and Client Secret)
- Your Xero tenant ID (obtainable by calling the Xero connections endpoint after authenticating)
- A Retool account with permission to create and configure Resources
- Understanding of Xero's Accounting API structure (invoices, contacts, bank transactions)

## Step-by-step guide

### 1. Create a Xero OAuth 2.0 application

Go to developer.xero.com and sign in with your Xero credentials. Navigate to 'My Apps' and click 'New app'. Choose the app type — for a Retool integration that accesses your own Xero organisation, select 'Web app' to use the Authorization Code OAuth flow.

Fill in the application name (e.g., 'Retool Finance Dashboard'), your company's URL, and the redirect URI. For Retool Cloud, the redirect URI is https://oauth.retool.com/oauth/user/oauthcallback. For self-hosted Retool, use your BASE_DOMAIN value followed by /oauth/user/oauthcallback. You can add multiple redirect URIs if needed.

Under OAuth 2.0 scopes, select the permissions your dashboard needs. For a read-only financial dashboard: openid (required), profile (required), email (required), accounting.transactions.read (invoices, bills, bank transactions), accounting.contacts.read (customers, suppliers), and accounting.settings.read (organisation details). If your Retool app needs to create or update records, add the write variants of these scopes.

After saving the app, Xero shows your Client ID. Click 'Generate a secret' to create your Client Secret. Copy both values — they will not be shown again. Store them securely; you'll paste them into the Retool Resource configuration in the next step.

**Expected result:** You have a Xero OAuth 2.0 application with a Client ID and Client Secret, the correct scopes, and the Retool redirect URI configured.

### 2. Configure the Xero REST API Resource in Retool

In Retool, open the Resources tab from the left sidebar navigation. Click 'Create a resource' and select 'REST API'. Name the resource 'Xero API'.

Set the base URL to https://api.xero.com/api.xro/2.0. This is the base URL for Xero's Accounting API v2 — all queries targeting accounting data (invoices, contacts, transactions) will use paths relative to this URL.

For authentication, select 'OAuth 2.0'. Enter the Client ID and Client Secret from your Xero developer application. Set the Authorization URL to https://login.xero.com/identity/connect/authorize and the Token URL to https://identity.xero.com/connect/token. In the Scope field, enter the scopes you configured: openid profile email accounting.transactions.read accounting.contacts.read accounting.settings.read (space-separated).

Next, add a custom header that all Xero queries require. Under 'Headers', click 'Add header'. Set the name to Xero-tenant-id. For the value, you have two options: enter your specific tenant ID directly if you're only connecting to one organisation, or use a configuration variable with {{ retoolContext.configVars.XERO_TENANT_ID }} to make it configurable without changing the resource settings. Also add an Accept header with value application/json.

Save the resource and click 'Connect' to complete the OAuth flow. Retool will open a Xero authentication popup — sign in and grant access to your organisation. After authorizing, the resource status should show as connected.

**Expected result:** The Xero API resource is configured in Retool with OAuth 2.0 authentication and the required Xero-tenant-id header. The resource shows as connected.

### 3. Retrieve your Xero tenant ID

The Xero-tenant-id is essential — every API call to Xero's Accounting API requires it to identify which organisation you're querying. To find your tenant ID, create a temporary resource or query pointing to Xero's connections endpoint.

Create a new query in a Retool app. Select your Xero API resource. Set the method to GET and the path to /Connections. Note: this endpoint uses the Xero base URL https://api.xero.com (not the Accounting API URL), so you may need to override the base URL in the query or create a separate 'Xero Identity' resource with base URL https://api.xero.com.

Run the query. The response is an array of connection objects, each containing tenantId (the value you need), tenantType (ORGANISATION or PRACTICE), tenantName (your Xero organisation name), and createdDateUtc. Copy the tenantId value for the organisation you want to connect to.

For accounting firms managing multiple clients, store all tenant IDs in a configuration variable or Retool Database table with associated client names. Build a Select dropdown in your Retool apps that lets users choose a client organisation — then pass the corresponding tenant ID in the Xero-tenant-id header of each query using {{ organisationSelect.value }}.

```
// Response from GET https://api.xero.com/connections
// Example response structure:
[
  {
    "id": "abc123de-f456-7890-abcd-ef1234567890",
    "authEventId": "abc123de-f456-7890-abcd-ef1234567890",
    "tenantId": "12345678-abcd-1234-abcd-123456789012",
    "tenantType": "ORGANISATION",
    "tenantName": "Acme Ltd",
    "createdDateUtc": "/Date(1609459200000)/",
    "updatedDateUtc": "/Date(1609459200000)/"
  }
]
```

**Expected result:** You have retrieved your Xero tenant ID and updated the Xero-tenant-id header in the Resource settings. Queries can now successfully target your Xero organisation.

### 4. Build invoice and financial overview queries

With the resource configured and tenant ID in place, create the core data queries for your financial dashboard. In a new Retool app, create a query named 'getInvoices'. Select the Xero API resource, set method to GET, and path to /Invoices. Add URL parameters: Type (set to ACCREC for accounts receivable invoices, or leave blank for all), Statuses (set to AUTHORISED,SUBMITTED for unpaid invoices), page (set to {{ pagination.currentPage || 1 }} for pagination), and pageSize (set to 100).

Xero's Invoices endpoint returns detailed invoice objects with fields for contact name, amounts, due dates, and line items. Create a second query 'getContacts' targeting /Contacts with parameters like where (Xero's filter syntax, e.g., IsCustomer=true) and summaryOnly=true for faster loading.

Create a third query 'getBankSummary' targeting /BankAccounts to pull account balances. For bank transactions, use /BankTransactions with date range parameters.

Note that Xero uses its own date format: /Date(1609459200000+0000)/ for JSON responses. Your transformer must parse these. Create a transformer to convert Xero dates and reshape the response for the dashboard.

```
// JavaScript transformer for Xero invoice data
const invoices = data.Invoices || [];

// Helper: parse Xero's /Date(timestamp+offset)/ format
function parseXeroDate(xeroDate) {
  if (!xeroDate) return null;
  const match = xeroDate.match(/\/Date\((\d+)[+-]\d+\)\//);
  if (!match) return xeroDate;
  return new Date(parseInt(match[1])).toLocaleDateString('en-GB', {
    year: 'numeric', month: 'short', day: 'numeric'
  });
}

return invoices.map(inv => ({
  invoiceNumber: inv.InvoiceNumber || 'N/A',
  contact: inv.Contact?.Name || 'Unknown',
  status: inv.Status,
  issueDate: parseXeroDate(inv.Date),
  dueDate: parseXeroDate(inv.DueDate),
  total: `${inv.CurrencyCode} ${inv.Total?.toFixed(2) || '0.00'}`,
  amountDue: `${inv.CurrencyCode} ${inv.AmountDue?.toFixed(2) || '0.00'}`,
  daysOverdue: inv.DueDate
    ? Math.max(0, Math.floor((Date.now() - parseInt(inv.DueDate.match(/\d+/)[0])) / 86400000))
    : 0,
  invoiceId: inv.InvoiceID
}));
```

**Expected result:** Invoice, contact, and bank queries return data and the transformer correctly parses Xero's date format. The dashboard is ready for components to be bound to this data.

### 5. Build the financial dashboard UI

Build the Retool financial dashboard components. At the top of the canvas, add a Stats Row using three Statistic components showing total accounts receivable (sum of AmountDue for unpaid ACCREC invoices), total overdue amount (invoices where daysOverdue > 0), and number of outstanding invoices.

Bind the Statistic component values using JavaScript expressions: {{ getInvoices_transformed.value.reduce((sum, inv) => sum + parseFloat(inv.amountDue.replace(/[^0-9.]/g, '') || 0), 0).toFixed(2) }}.

Below the stats, add a Bar Chart component. Set the dataset to a query or transformer that groups invoices by aging bucket. Use the 'Group by' feature in a separate transformer that categorises invoices as 'Current (0-30)', 'Overdue 31-60', 'Overdue 61-90', and 'Overdue 90+'.

Add a Table component showing the transformed invoice data. Configure columns: invoiceNumber, contact, status (as a Tag), issueDate, dueDate, amountDue, and a 'View in Xero' action button that constructs a Xero web URL and opens it in a new tab.

Add filter components above the table: a Select for invoice status (AUTHORISED, SUBMITTED, PAID), a TextInput for contact name search, and a Date Range picker for due date filtering. Connect these to the query parameters in your getInvoices query. For complex multi-resource dashboards combining Xero with your CRM or payment system, RapidDev's team can help design the data joining logic and query architecture.

```
// Transformer to group invoices into aging buckets for the bar chart
const invoices = getInvoices_transformed.value || [];

const buckets = {
  'Current (0-30 days)': 0,
  'Overdue 31-60 days': 0,
  'Overdue 61-90 days': 0,
  'Overdue 90+ days': 0
};

invoices.forEach(inv => {
  const days = inv.daysOverdue || 0;
  const amount = parseFloat(inv.amountDue.replace(/[^0-9.]/g, '') || 0);
  if (days <= 30) buckets['Current (0-30 days)'] += amount;
  else if (days <= 60) buckets['Overdue 31-60 days'] += amount;
  else if (days <= 90) buckets['Overdue 61-90 days'] += amount;
  else buckets['Overdue 90+ days'] += amount;
});

return Object.entries(buckets).map(([label, value]) => ({
  bucket: label,
  amount: parseFloat(value.toFixed(2))
}));
```

**Expected result:** The Retool financial dashboard displays outstanding invoice totals, an aging chart, and a filterable invoice table. Users can filter by status, contact, and date range and click through to view invoices directly in Xero.

## Best practices

- Store your Xero tenant ID in a Retool configuration variable (Settings → Configuration Variables) rather than hardcoding it in Resource headers — this makes switching between Xero organisations straightforward without editing the resource
- Use read-only OAuth scopes (accounting.transactions.read, accounting.contacts.read) for dashboard-only apps — only add write scopes if your Retool app actually creates or modifies Xero data
- Implement query caching for financial summary data that doesn't change frequently — a 5-minute cache on the accounts receivable total reduces Xero API calls without sacrificing data freshness
- For multi-client accounting firm setups, store tenant IDs and client names in a Retool Database table and build a client selector dropdown that passes the selected tenant ID in query headers
- Parse Xero's /Date(timestamp)/ format in a reusable transformer function that you call across all date fields — define it once and call it consistently rather than duplicating the regex in every transformer
- Use Xero's 'where' parameter for server-side filtering (e.g., Status=="AUTHORISED" AND AmountDue>0) rather than loading all records and filtering in Retool — Xero's API supports compound filter expressions
- When building payment workflows that write back to Xero (creating invoices, recording payments), always add a confirmation modal before executing write queries to prevent accidental duplicate payments or data entry errors

## Use cases

### Accounts receivable aging dashboard

Build a dashboard showing all outstanding invoices grouped by aging buckets (0-30 days, 31-60 days, 61-90 days, 90+ days). Finance teams can filter by contact, see total outstanding amounts per bucket, and click through to individual invoice details — all in one view that Xero's native reports don't easily produce.

Prompt example:

```
Build a Retool app with a Bar Chart showing invoice counts by aging bucket and a Table below showing individual outstanding invoices with columns for contact name, invoice number, due date, amount due, and days overdue. Add a Select input to filter by contact and a Date Range picker to filter by due date.
```

### Supplier bill payment operations panel

Build a bill approval and payment workflow where finance managers can see all bills awaiting payment, filter by supplier and amount, mark bills as approved, and trigger payments directly from Retool. The panel connects Xero's bills data with your internal approval process before executing payments.

Prompt example:

```
Build a Retool dashboard showing Xero bills with AUTHORISED status. Include columns for supplier name, bill date, due date, amount, and an Approve button. When approved, mark the bill status and update a separate tracking database. Add a total outstanding amount summary at the top.
```

### Multi-client accounting overview for accounting firms

For accounting firms managing multiple Xero organisations, build a master dashboard that loops through multiple tenant IDs and shows a summary of each client's outstanding invoices, bank balances, and overdue bills on a single screen — eliminating the need to switch between Xero accounts.

Prompt example:

```
Build a Retool app with a Select dropdown listing client organisations (each mapped to a Xero tenant ID). When a client is selected, load their Xero dashboard showing total accounts receivable, total accounts payable, current bank balance, and a list of the 10 most recent transactions.
```

## Troubleshooting

### Xero API queries return 403 Forbidden with message 'AuthenticationUnsuccessful' or 'TenantIdNotFound'

Cause: The Xero-tenant-id header is missing, incorrect, or not matching the authenticated user's connected organisations. This is the most common Xero API error.

Solution: Verify your tenant ID by running a GET query to https://api.xero.com/connections (you may need a separate resource with base URL https://api.xero.com and no Accounting API path). Copy the tenantId field (not the id field) from the response and update your Resource's Xero-tenant-id header. Ensure the header name is exactly 'Xero-tenant-id' (case-sensitive, with hyphen).

### OAuth token expires and Retool queries fail with 401 Unauthorized after 30 minutes

Cause: Xero access tokens expire after 30 minutes. Retool should automatically refresh tokens using the refresh token, but this fails if the refresh token has expired (Xero refresh tokens expire after 60 days of inactivity).

Solution: In the Resource settings, ensure 'Automatically refresh tokens' is enabled. If the refresh token has expired, the resource will need to be re-authenticated: open Resource settings, scroll to the OAuth section, and click 'Reconnect'. Users may need to re-authorize through the Xero OAuth flow. To avoid this for production dashboards, ensure the Retool app is used at least once every 60 days to keep the refresh token active.

### Date fields in Xero responses display as '/Date(1609459200000+0000)/' instead of readable dates

Cause: Xero uses a legacy .NET date format (Microsoft JSON Date format) for date fields in JSON responses instead of ISO 8601 format.

Solution: Use a JavaScript transformer to parse Xero dates. The pattern /Date(timestamp+offset)/ contains a Unix timestamp in milliseconds. Extract the number and pass it to JavaScript's Date constructor.

```
// Parse Xero date format in a transformer
function parseXeroDate(xeroDate) {
  if (!xeroDate) return '';
  const match = xeroDate.match(/\/Date\((\d+)[+-]\d+\)\//);
  if (!match) return xeroDate;
  return new Date(parseInt(match[1])).toLocaleDateString();
}
// Usage: parseXeroDate(invoice.DueDate)
```

### Xero API returns fewer records than expected — page 1 shows 100 results but there are clearly more invoices

Cause: Xero's Invoices endpoint is paginated with a default page size of 100. Without explicit pagination parameters, only the first page is returned.

Solution: Add page and pageSize parameters to your query (page defaults to 1, pageSize maximum is 1000 for the Invoices endpoint). Implement a multi-page fetch using a Retool Workflow if you need all records: loop through pages until the response returns fewer results than pageSize, accumulating all results. For dashboards, use Retool's built-in Table pagination and pass the current page to the Xero page parameter.

## Frequently asked questions

### What is a Xero tenant ID and why is it required for every API call?

A Xero tenant ID is a UUID that identifies a specific Xero organisation (or practice). Xero's OAuth 2.0 model allows a single set of API credentials to be authorised across multiple organisations — the tenant ID tells Xero which organisation to query for each request. Without it, Xero doesn't know which of your connected organisations you're targeting. You retrieve it by calling the /connections endpoint immediately after authentication.

### Can one Retool resource access multiple Xero organisations?

Yes. Configure the Resource with a dynamic Xero-tenant-id header using a component value: set the header value to {{ organisationSelect.value }} and populate the Select dropdown with all your tenant IDs. When users switch organisations, the header value changes and subsequent queries target the new organisation. Each authenticated OAuth token can access all organisations the user has been invited to in Xero.

### Does Xero's API support real-time data or is there a delay?

Xero's API provides near-real-time data — changes made in Xero are typically available via the API within seconds. However, Xero enforces rate limits of 60 calls per minute and 5,000 calls per day for standard connections. For high-frequency dashboards, implement Retool query caching to avoid hitting these limits, and use Xero's LastModifiedBefore/LastModifiedAfter parameters to only fetch recently changed records.

### Can I use this Retool integration to create invoices or record payments in Xero?

Yes, if your OAuth application includes write scopes (accounting.transactions). You can POST to /Invoices to create invoices, POST to /Payments to record payments, and PUT to update existing records. Always add confirmation modals and input validation in Retool before executing write operations, as Xero has strict validation rules (e.g., an invoice can only be paid if its status is AUTHORISED and the payment amount matches or is less than the outstanding balance).

### What Xero plan is required to use the API?

Xero's API access is available across all paid Xero plans (Starter, Standard, Premium, Ultimate). The free trial also includes API access. There are no per-API-call charges — usage is billed as part of your Xero subscription. However, if you're building an integration to distribute to multiple Xero users, you may need to review Xero's API Partner programme requirements.

---

Source: https://www.rapidevelopers.com/retool-integrations/xero
© RapidDev — https://www.rapidevelopers.com/retool-integrations/xero
