---
title: "How to Export Data from Xero: API Limits, Methods & Portability"
slug: how-to-export-data-from-xero-api-limits-methods-portability
date: 2026-09-17
author: Nachi Raman
categories: [Xero]
excerpt: "Learn how to export data from Xero using UI exports, the Accounting API, and webhooks — with real rate limits, new 2026 pricing tiers, and data portability details."
tldr: "Xero has no single export-all button. Use UI CSV exports for one-off backups, the API (60 calls/min, 5,000/day) for programmatic extraction, and webhooks for real-time sync — but plan around the new March 2026 egress pricing."
canonical: https://clonepartner.com/blog/how-to-export-data-from-xero-api-limits-methods-portability
---

# How to Export Data from Xero: API Limits, Methods & Portability


# How to Export Data from Xero: API Limits, Methods & Portability

[Xero](https://clonepartner.com/blog/blog/xero-vs-wave-2026-architecture-apis-and-scaling) gives you three ways to get data out: manual CSV exports from the UI, the Accounting API (with its rate limits and new egress pricing), and third-party extraction tools. There is no single "export everything" button — you need to pull data from individual areas of Xero and combine reports from the reports screen to get a full picture of your organisation's financials.

This guide covers each extraction method in detail, with the real constraints you'll hit, the API limits that matter, and what to do about Xero's new usage-based pricing model that took effect in March 2026.

## What Data Can You Export from Xero?

**Xero's exportable data** includes your chart of accounts, contacts, invoices, bills, credit notes, products and services, fixed assets, bank transactions, and a range of financial reports — but each must be exported separately.

Xero's own documentation is clear on this point: to export all of an organisation's information, you need to pull data from individual sections of Xero and then export reports from the reports screen. There is no bulk "download my data" option.

Here's what you can export directly from the Xero UI:

| Data type | Export format | Where to find it |
|---|---|---|
| Chart of accounts | CSV | Accounting → Advanced → Chart of Accounts → Export |
| Contacts | CSV | Contacts → All Contacts → Export |
| Invoices | CSV | Sales → Invoices → Export |
| Bills | CSV | Purchases → Bills → ⋮ → Export bills |
| Credit notes | CSV | Via invoices/bills export |
| Products & services | CSV | Business → Products and services → Export |
| Fixed assets | CSV | Accounting → Advanced → Fixed Assets → Export |
| Reports (P&L, Balance Sheet, etc.) | CSV, Excel, PDF | Accounting → Reports → select report → Export |
| General ledger | CSV | Accounting → Reports → General Ledger → Download |

> [!WARNING]
> You cannot export repeating invoice or bill templates from Xero. Special characters in transactions, contact records, or organisation details can also cause CSV exports to fail — remove them and retry.

## What Should You Export Before Cancelling Xero?

Export your chart of accounts, contacts, invoices, bills, and fixed asset list at a minimum. Xero's official guidance also recommends exporting several reports that are harder to reconstruct elsewhere:

- **Account Transactions report** — a complete list of all transactions. If you use multicurrency, select all columns including FX columns.
- **Receivable Invoice Detail report** — details of invoices, credit notes, overpayments, and prepayments.
- **Payable Invoice Detail report** — the same for bills.
- **Inventory Item List report** — item detail and transaction history.
- **Bank Reconciliation report** — reconciled bank transactions.

This matters because of Xero's data retention policy after cancellation. Once you cancel, your data is archived and inaccessible — you cannot log in, download, or run reports. Xero retains archived data for up to seven years, but accessing it requires reactivating (and paying for) a subscription. If you cancelled a free trial, reactivation is not possible at all.

## How to Export Data from Xero Using the UI

The manual export process is straightforward but tedious for organisations with years of transaction history.

**For invoices and bills:**
1. Navigate to **Sales → Invoices** (or **Purchases → Bills**).
2. Apply date-range filters or status filters to narrow the dataset.
3. Click **Export** and choose CSV.
4. Save the downloaded file.

**For the chart of accounts:**
1. Go to **Accounting → Advanced → Chart of Accounts**.
2. Click **Export** in the header — a CSV file downloads automatically.

**For reports:**
1. Go to **Accounting → Reports**.
2. Select the report you need (e.g., Budget Manager, Trial Balance).
3. Set the period and any other parameters.
4. Scroll to the bottom and click **Export**, then choose CSV or Excel.

**For contacts:**
1. Open **Contacts → All Contacts**.
2. Select the contacts you want (or all).
3. Click **Export** to download a CSV.

The UI method works for one-off extractions and small organisations. It breaks down when you need recurring exports, have tens of thousands of transactions, or need to pull data from more than a handful of entity types at once.

## How the Xero API Works for Data Extraction

**The Xero Accounting API** (v2.0, at `https://api.xero.com/api.xro/2.0/`) is a RESTful interface that provides programmatic read and write access to accounting data including invoices, contacts, bank transactions, payments, journals, and reports. It accepts and returns JSON or XML, with JSON recommended.

Every API request requires a `Xero-Tenant-Id` header identifying which Xero organisation you're accessing. A single OAuth 2.0 connection can be authorised for multiple tenants, which is how most integration apps serve accounting firms with dozens of client organisations.

**OAuth 2.0 token behaviour:** Access tokens expire after **30 minutes**. Refresh tokens are valid for **60 days** from the last use and rotate on each use — meaning your application must store the new refresh token returned with each access token refresh, or the connection will break. Refresh token rotation is one of the most common failure points in extraction pipelines; if a refresh call fails (e.g., due to a network timeout), you must reauthorise the user from scratch. Build retry logic specifically around token refresh operations with a separate error path from standard API call failures.

Key endpoints for data extraction:

- `GET /Invoices` — retrieve invoices, with pagination
- `GET /Contacts` — retrieve contacts (supports `summaryOnly=true` for lighter payloads)
- `GET /BankTransactions` — bank statement lines
- `GET /Payments` — payment records
- `GET /ManualJournals` — journal entries
- `GET /Reports` — pre-built financial reports (P&L, Balance Sheet, Trial Balance)
- `GET /Accounts` — chart of accounts

> [!TIP]
> Use the `If-Modified-Since` header on GET requests to retrieve only records that changed after a given timestamp. This is the single most effective way to reduce both API calls and data egress — especially under Xero's new pricing model. Static data like chart of accounts, tax rates, and currencies changes rarely; cache these locally and refresh weekly rather than on every sync cycle.

### Pagination: 100 to 1,000 Results Per Page

Xero's API defaults to 100 results per page for backward compatibility, but supports up to 1,000 results per page using the `pagesize` parameter. This applies to invoices, contacts, bank transactions, credit notes, payments, manual journals, quotes, and purchase orders.

Retrieving invoices without pagination returns summary data only — no line items. To get line item detail, you must either request a single invoice by ID or use the `page` parameter.

For the Contacts endpoint specifically, requests returning more than 100,000 contacts will be denied with a 400 response. Requests using unoptimised fields for filtering that would return 100k+ contacts are also blocked.

```python
import requests
import time

headers = {
    "Authorization": "Bearer YOUR_ACCESS_TOKEN",
    "Xero-Tenant-Id": "YOUR_TENANT_ID",
    "Accept": "application/json"
}

def fetch_invoices_with_retry(since_date, page=1, max_retries=5):
    """Fetch invoices with exponential backoff on rate limit errors."""
    for attempt in range(max_retries):
        response = requests.get(
            "https://api.xero.com/api.xro/2.0/Invoices",
            headers={**headers, "If-Modified-Since": since_date},
            params={"page": page, "pageSize": 1000}
        )
        
        if response.status_code == 200:
            return response
        elif response.status_code == 429:
            # Respect Retry-After header with jitter to avoid thundering herd
            retry_after = int(response.headers.get("Retry-After", 60))
            jitter = retry_after * (0.5 + attempt * 0.5)  # Exponential backoff with jitter
            print(f"Rate limited. Waiting {jitter:.0f}s (attempt {attempt + 1}/{max_retries})")
            time.sleep(jitter)
        elif response.status_code == 401:
            raise Exception("Token expired — trigger refresh token flow")
        else:
            response.raise_for_status()
    
    raise Exception(f"Max retries exceeded for page {page}")
```

## What Are Xero's API Rate Limits?

Xero enforces per-tenant, per-app rate limits: **60 API calls per minute**, **5,000 API calls per day** (for Core tier and above), and **5 concurrent requests**. There is also an app-wide limit of 10,000 calls per minute across all connected tenants.

Apps on the **Starter tier** (the free tier) are limited to 1,000 calls per day instead of 5,000. Uncertified apps are restricted to 25 connected organisations.

When you exceed a limit, Xero returns HTTP 429 ("Too Many Requests") with an `X-Rate-Limit-Problem` header indicating which limit was breached and a `Retry-After` header telling you how many seconds to wait.

The response headers on every call tell you where you stand:

| Header | What it means |
|---|---|
| `X-MinLimit-Remaining` | Calls left in the current minute window |
| `X-DayLimit-Remaining` | Calls left in the current day window |
| `X-AppMinLimit-Remaining` | App-wide calls left in the current minute |

Rate limits are enforced on a fixed window that resets at different times for each tenant. There is no way to request higher limits — Xero's documentation states they are unable to raise or lift limits for any app.

**Error handling strategy:** When you receive a 429, read the `Retry-After` value and implement exponential backoff with jitter. Base wait = `Retry-After` seconds; add random jitter of ±20% to prevent multiple workers from retrying simultaneously (thundering herd problem). For batch operations, implement a token bucket or leaky bucket algorithm rather than simple sleep() calls — this allows burst absorption while maintaining average throughput within limits.

**Handling partial batch failures:** If a batch request (up to 50 elements, 3.5 MB limit) returns a 200 with partial errors, Xero embeds per-element validation errors in the response body. Parse the response for elements with `StatusAttributeString: "ERROR"` and requeue only the failed elements rather than retrying the entire batch.

### How Many Records Can You Extract Per Day?

With 5,000 daily calls and up to 1,000 records per page, a single org could theoretically extract up to 5 million records per day if every call returns a full page. In practice this number is lower because you need calls for auth token refreshes, contact lookups, payment records, and error retries.

Here is a worked example for a realistic full-historical extraction of a five-year-old Xero organisation:

| Data type | Estimated records | Pages at 1,000/page | API calls |
|---|---|---|---|
| Invoices (with line items) | 12,000 | 12 | 12 |
| Contacts | 3,000 | 3 | 3 |
| Bank transactions | 25,000 | 25 | 25 |
| Payments | 10,000 | 10 | 10 |
| Manual journals | 500 | 1 | 1 |
| Reports (12 monthly P&Ls + TB + BS) | — | — | 26 |
| Token refreshes (1 per 30 min of work) | — | — | ~8 |
| **Total** | | | **~85 calls** |

This example organisation completes well within the 5,000-call daily limit at the Core tier. The constraint becomes relevant for platforms syncing **continuously** across hundreds of organisations — at that scale, 5,000 calls per tenant per day with 60/minute bursting requires careful scheduling to distribute sync jobs across the day rather than batching them overnight.

For invoice syncing with full contact and payment verification (3–6 API calls per invoice), the effective ceiling is approximately 833–1,667 invoices per day within the daily limit, depending on whether you can batch contact lookups.

## Xero's API Pricing: What Changed in March 2026

**Xero's API pricing model**, introduced on March 2, 2026, replaced the previous 15% App Store revenue share with a five-tier usage-based structure charged in AUD. The two billing metrics are the number of connected Xero organisations and monthly data egress volume.

The tiers are:

| Tier | Price (AUD/month) | Connected orgs | Daily API calls | Included egress |
|---|---|---|---|---|
| Starter | $0 | Up to 5 | 1,000 | Minimal |
| Core | Flat fee | Up to 1,000 | 5,000 | 10 GB |
| Plus | Flat fee | Up to 5,000 | 5,000 | 50 GB |
| Advanced | Flat fee | Up to 10,000 | 5,000 | 250 GB |
| Enterprise | Negotiated | 10,000+ | 5,000 | Negotiated |

Overage is charged at **$2.40 AUD per additional gigabyte** of data downloaded from Xero's APIs. For apps that rely on full-sync architectures — polling all records on a schedule — this can add up fast.

**Worked cost example:** A platform syncing 500 organisations nightly, pulling 50 MB of data per org per sync cycle, generates 25 GB of monthly egress. On the Core tier (10 GB included), the overage is 15 GB × $2.40 AUD = $36 AUD/month. Switching to delta sync (assuming 80% of records are unchanged each night) reduces egress to approximately 5 GB/month — within the Core tier's included allowance with no overage charge.

There is no grandfathering. Existing developers received at least 30 days' notice before their individual app migration dates.

## The AI Training Prohibition in Xero's Developer Terms

Xero's updated developer terms contain an explicit prohibition on using API-obtained data to train or contribute to AI and machine learning models. This is not a minor policy footnote — it has direct implications for how you architect downstream data pipelines.

The prohibition covers:
- Using Xero API data as training input for any ML model, including fine-tuning pre-trained models
- Contributing Xero API data to shared datasets used for model training
- Storing API-extracted data in a form that could later be used for training without additional consent

**What compliant downstream storage looks like:**
- Extracted data used for business reporting, reconciliation, or migration should be stored with clear purpose limitation documentation
- If your pipeline involves any ML component (anomaly detection, forecasting, categorisation), ensure that component is trained on data obtained through a separate, explicitly consented channel — not the Xero API extraction
- Review your data retention policy: storing Xero API data indefinitely "for future use" creates compliance risk if that future use includes model training

If you are building a product that combines Xero financial data with ML features, obtain explicit user consent outside of the standard Xero OAuth flow, and maintain clear data provenance records showing which data was API-sourced and which was user-provided or separately consented.

## Using Webhooks to Reduce Polling

**Xero webhooks** are HTTP POST notifications sent to a URL you register when specific accounting data changes, eliminating the need to poll endpoints repeatedly.

Webhook support currently covers **invoices**, **contacts**, and **credit notes**. Credit notes were one of the most heavily polled areas of Xero's API, generating roughly 16 million GET calls per month across the ecosystem before webhooks were available. Some partners reported a nearly 50% reduction in GET calls after switching from polling to webhook-based sync.

Webhook payloads are thin: they tell you *that* something changed (event category, resource ID, tenant ID) but not *what* changed. You still need an authenticated API call to fetch the full record using the `resourceUrl` provided in the event. Each notification can batch up to 200 events.

**Delivery guarantees and reliability characteristics:**
- Xero webhooks are designed for **at-least-once delivery** — duplicate events are possible and your endpoint must be idempotent (processing the same event twice must produce the same result).
- Expected latency from event trigger to webhook delivery is typically seconds, but Xero does not publish a contractual SLA for delivery latency.
- Events that cannot be delivered are retried by Xero, but there is no published retry schedule or maximum retry count before events are dropped (separate from the subscription disable behaviour below).

**Key operational constraints:**
- Your endpoint must respond within **5 seconds** with HTTP 200.
- Xero validates your endpoint with an "Intent to Receive" handshake using **HMAC-SHA256 signature verification** before activating the webhook.
- If your endpoint fails to respond successfully for **24 hours**, Xero disables the subscription and sends an email notification.
- Events that occur while your subscription is disabled are saved for up to **31 days** and replayed once the subscription is healthy.
- **Payroll resources are not supported** via webhooks. Use incremental polling with `If-Modified-Since` for payroll data.
- **Audit trail and history logs** are not accessible via webhooks or the Accounting API. These are only visible in the Xero UI.

Because webhooks guarantee at-least-once rather than exactly-once delivery, store a processed event ID for each incoming notification and skip reprocessing if the ID has been seen. Use the `resourceUrl` and a `lastModifiedUtc` comparison to detect whether the API-fetched record is actually newer than your stored version before committing an update.

## GDPR, Data Portability, and Your Rights

**Data portability** under GDPR (Article 20) gives individuals the right to receive their personal data in a structured, machine-readable format. Xero supports data access, correction, deletion, and export requests to comply with GDPR Articles 15–22.

For practical purposes, Xero's GDPR compliance means:
- You can export contact records and transaction history to satisfy Subject Access Requests.
- Data portability requests require you to provide a machine-readable file (CSV or JSON) containing the personal data you hold on a contact.
- GDPR erasure requests are handled by contacting Xero support, which puts the contact into a **"deep archive" state**: the contact record is flagged as deleted, removed from active views, and pseudonymised — personal identifiers (name, email, address) are replaced with anonymised placeholders while the underlying transaction records are retained for legal and accounting compliance. The contact cannot be reactivated or its original data recovered after archival.

Xero stores data in data centres in the US and Australia, relying on Standard Contractual Clauses for lawful EU data transfers. New Zealand (where Xero is headquartered) is recognised by the EU as an adequate country for receiving personal data.

As a Xero subscriber, you are typically the **data controller**, and Xero operates as the **data processor**. The compliance burden for responding to data subject requests falls on you, not Xero — though Xero provides the tools to extract the data you need. Subject Access Requests should be fulfilled within 30 days under GDPR; ensure your extraction workflow can produce a complete, structured export for a specific contact within that window.

## When the API Isn't Enough: Edge Cases and Limitations

Several categories of Xero data are difficult or impossible to extract cleanly:

- **Repeating invoice/bill templates** cannot be exported via the UI or API.
- **Audit trail / history logs** are visible in the UI but not directly available through the API.
- **File attachments** (receipts, PDFs attached to invoices) require separate API calls via the Files API, which has a 10 MB per-file upload limit. Attachments must be fetched individually per invoice — there is no bulk attachment export endpoint.
- **Payroll data** lives on a separate API with region-specific endpoints. AU/NZ payroll and UK payroll use different data structures with no interchangeable format. Payroll endpoints have their own separate rate limits from the Accounting API.
- **Archived contacts** are excluded by default — you need `IncludeArchived=true` to capture them.
- **Report endpoints** return pre-computed reports, not raw transaction data. The report format is fixed and cannot be customised via API. For raw journal-level data, use the `GET /Journals` endpoint instead.
- **Multicurrency FX rates** used in historical transactions are not directly retrievable as a structured dataset — they must be reconstructed from individual transaction records.
- **Tracking categories** applied to transactions are included in line-item data but must be mapped against `GET /TrackingCategories` to resolve names — IDs alone are returned in transaction records.

## Choosing the Right Extraction Method

The right approach depends on volume, frequency, and what you're doing with the data once it's out.

| Scenario | Best method | Why |
|---|---|---|
| One-time backup before cancellation | UI export + reports | No API setup needed; covers all critical data |
| Periodic reporting (weekly/monthly) | API with delta sync | `If-Modified-Since` keeps calls and egress low |
| Real-time sync to another platform | Webhooks + API | Webhooks trigger; API fetches changed records |
| Full historical migration to new system | API with pagination | Page through all records; batch where possible |
| Quick ad-hoc data pull | UI export | Fastest for small datasets |
| GDPR Subject Access Request | UI export (contacts) + API | Combine contact CSV with transaction history |
| Multi-entity accounting firm extraction | API with multi-tenant OAuth | Single app connection covers all client orgs |

For [one-time migrations or complex historical extractions](https://clonepartner.com/blog/blog/quickbooks-to-xero-migration-jet-convert-limits-data-mapping), the biggest challenges are:
1. **Multicurrency transactions** — FX rates are embedded per transaction, not stored as a reference table
2. **Partial payments** — invoices with split payments require joining the Payments endpoint to reconstruct the full payment history
3. **Credit note allocations** — credit notes applied to invoices are not always obvious in the invoice record; check the `CreditNotes` array on each invoice response
4. **Tracking category mapping** — resolve category IDs to names before export, not after, to avoid a second API pass

## Making It Practical

Exporting data from Xero is fully achievable, but it's not a single-click operation. The UI gets you basic CSVs. The API gives you programmatic control but comes with hard rate limits (60/min, 5,000/day) and — since March 2026 — real costs attached to data egress ($2.40 AUD/GB over tier limits). Webhooks reduce polling overhead for invoices, contacts, and credit notes, but payroll and audit logs still require polling or UI-based extraction.

**Checklist before you start any extraction project:**

1. [Map every data entity you need](https://clonepartner.com/blog/blog/accounting-data-migration-checklist-the-10-point-plan) — don't assume invoices and contacts are sufficient
2. Estimate total record count and calculate API call budget (pages × endpoints × orgs)
3. Determine if delta sync is viable (it requires a reliable `lastModified` timestamp anchor)
4. Check whether you need file attachments — they multiply API calls significantly
5. Review Xero's developer terms if your downstream pipeline includes any ML component
6. If cancelling Xero, export critical reports *before* downgrading — especially Account Transactions with FX columns for multicurrency organisations
7. For multi-org setups, verify OAuth token coverage across all tenants before starting a large extraction job

Once your subscription is archived after cancellation, the only way back in is reactivation at current subscription prices. Xero does not offer a read-only archive access tier.

> Need to move data out of Xero — or sync it to another platform? ClonePartner has handled hundreds of accounting data migrations. We'll scope the extraction, handle the edge cases, and get your data where it needs to go. Book a 30-minute call and tell us what you're working with.
>
> [Talk to us](https://cal.com/clonepartner/meet?duration=30)

## Frequently asked questions

### Can you export all data from Xero at once?

No. Xero does not offer a single bulk export feature. You need to export data from individual areas (invoices, contacts, chart of accounts, bills, fixed assets) separately, and download reports from the reports screen. Each export produces a CSV, Excel, or PDF file depending on the data type.

### What are Xero's API rate limits?

Xero allows 60 API calls per minute, 5,000 calls per day (Core tier and above), and 5 concurrent requests per organisation per app. Starter tier apps are limited to 1,000 calls per day. There's also an app-wide cap of 10,000 calls per minute across all tenants. Exceeding any limit returns HTTP 429.

### What happens to my Xero data if I cancel my subscription?

Xero archives your data for up to seven years after cancellation. While archived, you cannot access it — logging in, exporting, and running reports are all disabled. You can regain access by reactivating (and paying for) a subscription. Free trial data cannot be recovered after cancellation.

### How much does Xero charge for API data egress?

Since March 2026, Xero charges $2.40 AUD per additional GB of data downloaded from its APIs beyond your tier's included allowance. The Core tier includes 10 GB, Plus includes 50 GB, and Advanced includes 250 GB per month. The Starter tier is free but limited to 5 connections and 1,000 daily API calls.

### Does Xero support webhooks for real-time data sync?

Yes, but only for invoices, contacts, and credit notes. Webhook payloads notify you that a record changed but don't include the data itself — you need a follow-up API call. Payroll and other data types still require polling with the If-Modified-Since header.
