ml-connector
XeroExpensify

Xero and Expensify integration

Xero runs your general ledger. Expensify tracks employee expenses. Connecting the two means approved expense reports flow directly into Xero as manual journal entries without re-keying, and expense categories map cleanly to your chart of accounts. ml-connector bridges the two platforms, pulls Expensify reports on a schedule, and posts the numbers into Xero at the GL account and cost center level. The result is an accurate, auditable expense ledger that stays in sync with your employee reimbursements.

How Xero works

Xero is a cloud-based accounting platform that exposes invoices, payments, contacts, accounts, tracking categories, manual journals, and bank transactions through the Xero Accounting API via REST with JSON payloads. All calls target the base URL https://api.xero.com/api.xro/2.0/ and require the Xero-tenant-id header to route to a specific organization. Authentication uses OAuth2 Authorization Code flow with access tokens that expire after 30 minutes and refresh tokens valid for 60 days. Rate limits are enforced: 5 concurrent calls, 60 per minute per tenant, and 5000 per day per organization. Xero exposes webhooks for invoice, payment, manual journal, and contact events, though webhook payloads contain metadata only and require a follow-up GET to fetch the full record. Delta sync is also supported via the If-Modified-Since header for polling-based workflows.

How Expensify works

Expensify is a cloud-based expense management platform that tracks employee expenses, manages approval workflows, and reconciles corporate card transactions. The platform exposes expense reports and individual expenses through a REST API by posting JSON payloads to https://integrations.expensify.com/Integration-Server/ExpensifyIntegrations. Authentication uses an API key pair (partnerUserID and partnerUserSecret) generated in the Expensify integrations portal and passed with every request. Expensify operates polling-only mode with no webhooks; ml-connector exports reports using scheduled requests with date-range and status filters. There is no sandbox environment, so all API calls run against production. Expensify is expense-management focused: it tracks expense transactions, policies (workspaces), and tags that function as accounting dimensions, but does not expose invoice, purchase order, or payment entities. GL account codes are properties of expense categories configured per workspace. Corporate cards are read-only via API.

What moves between them

Approved expense reports flow from Expensify into Xero. ml-connector polls Expensify on a schedule you define (daily, weekly, or tied to a specific close period), pulls all reports in APPROVED or REIMBURSED status, and groups transactions by expense category and assigned cost center tag. Each report group is posted into Xero as a single manual journal entry, with the debit side split across GL accounts mapped from Expensify categories and the credit side pointing to a clearing account configured per workspace. Cost center and project tags in Expensify map to Xero tracking categories so expense allocations land on the correct cost center in the ledger. Posting is one-way: Xero is read-only in this flow. Historical reports can be re-posted if a prior posting fails or needs reversal.

How ml-connector handles it

ml-connector stores the Xero OAuth2 credentials and Expensify API key pair encrypted and manages token refresh transparently, re-authenticating if a call returns 401 or if the access token approaches expiry. For each Xero tenant, it accepts the tenant ID and a mapping of Expensify workspace categories to Xero GL accounts and tracking category codes. On the poll cycle, ml-connector requests reports from Expensify filtered by date range and approval status, iterates the expense transactions within each approved report, and sums them by GL account and cost center tag. It validates that each cost center tag exists as a tracking category in Xero before posting, so mismatched tags fail early with audit detail rather than posting to the wrong dimension. The manual journal is constructed with line items per GL account and cost center combination, all posted with the same journal date and a reference that includes the Expensify report ID for traceability. Xero rate limits may return HTTP 429 on high-volume posting, so ml-connector backs off and retries. Every report transaction carries full audit detail: the Expensify report ID, the journal ID in Xero, the status (pending, posted, failed), and any error message if posting failed. Reports can be replayed if needed.

A real-world example

A mid-sized professional services firm uses Xero for accounting and Expensify for employee expense tracking. Before the integration, finance staff exported approved reports from Expensify each week, manually entered them into Xero as journal entries, and reconciled the totals against employee reimbursement payments. This process was slow, error-prone, and hard to audit. With Xero and Expensify connected, each week's approved reports post automatically into Xero as journal entries keyed to the correct GL accounts and cost centers, with full traceability back to the original receipts. Finance closes the books faster, and there is a clear audit trail from expense to ledger entry.

What you can do

  • Poll approved expense reports from Expensify and post them into Xero as manual journal entries on a schedule you define.
  • Map Expensify expense categories to Xero GL accounts and cost center tags to Xero tracking categories so allocations land on the correct dimensions.
  • Refresh Xero OAuth2 tokens transparently and route requests to the correct Xero tenant using the tenant ID.
  • Validate cost center assignments against Xero tracking categories before posting, so mismatched tags fail early with audit detail.
  • Maintain a complete audit trail of every report, its posting status, and any errors, with the ability to replay or reverse postings if needed.

Questions

Which direction does data move between Xero and Expensify?
The main flow is from Expensify into Xero. Approved expense reports and their transactions move from Expensify into Xero as manual journal entries, mapped to GL accounts and cost centers. Xero is read-only in this integration; ml-connector does not write data back to Expensify.
How does the integration handle Xero's OAuth2 token expiry and multi-tenant routing?
ml-connector stores the Xero OAuth2 credentials encrypted and manages token refresh automatically. If a call returns 401 or the access token nears expiry, it re-authenticates transparently without intervention. For multi-tenant Xero deployments, ml-connector accepts the tenant ID and routes all requests using the Xero-tenant-id header, so each organization's reports post to the correct instance.
What happens if a cost center tag in Expensify does not match a tracking category in Xero?
ml-connector validates every cost center tag against Xero's configured tracking categories before posting the report. If a tag does not exist in Xero, the report fails validation early with a detailed audit entry, so errors are caught before any posting happens. The report can be corrected in Expensify and replayed once the tracking category is added to Xero.

Related integrations

Connect Xero and Expensify

Free to use. Add your credentials, ping your real systems, and see if we fit.

Get started