Xero API: Complete Developer Guide for Integration (2026) Chintan Prajapati April 3, 2026 11 min read IntroductionThe Xero API is the backbone of every custom accounting integration built on the Xero platform.Whether you are building a SaaS product that syncs invoices, an internal tool that automates bank reconciliation, or a reporting dashboard that pulls real-time financial data, the Xero API gives you programmatic access to the full accounting ledger.This guide is the single reference you need in 2026. It covers OAuth 2.0 authentication with PKCE, every major endpoint across six Xero APIs, rate limits, known limitations (with workarounds), webhooks, official SDKs, sandbox testing, and production-ready code examples in Python and Node.js.If you are evaluating Xero integration services or building one yourself, start here.TL;DR — Quick Start in 5 Minutes Protocol: RESTful JSON over HTTPS Auth: OAuth 2.0 with PKCE (no more OAuth 1.0a) Base URL: https://api.xero.com/api.xro/2.0/ Rate Limit: 60 calls/minute per tenant, 5,000/day SDKs: Official libraries for Python, Node.js, .NET, Java, Ruby, Go, PHP First API Call: Register an app at developer.xero.com, get your client ID, implement OAuth 2.0 flow, call GET /Invoices Need help? Our Xero integration team builds production-grade integrationsWhat is the Xero API? (Quick Overview)Xero exposes six distinct APIs, each serving a different domain of accounting and business operations.All follow the same RESTful conventions and share the same OAuth 2.0 authentication layer.APIBase URLPurposeAccounting APIapi.xero.com/api.xro/2.0/Core financials: invoices, contacts, payments, bank transactions, journals, chart of accounts, reportsAssets APIapi.xero.com/assets.xro/1.0/Fixed asset register, depreciation schedules, asset typesProjects APIapi.xero.com/projects.xro/2.0/Project tracking, time entries, project users, tasksPayroll APIapi.xero.com/payroll.xro/2.0/Employee records, pay runs, leave, timesheets (region-specific: AU, NZ, UK)Files APIapi.xero.com/files.xro/1.0/File uploads, folders, file associations to invoices/contactsBankFeeds APIapi.xero.com/bankfeeds.xro/1.0/Push bank statement data into Xero for reconciliationAll API requests require the Xero-Tenant-Id header, which identifies which Xero organization you are accessing.A single OAuth 2.0 connection can be authorized for multiple tenants. Use the GET /connections endpoint to list all authorized tenants and retrieve their IDs.Request & Response Format: All endpoints accept and return JSON by default. Set Content-Type: application/json and Accept: application/json headers.Some endpoints also support XML, but JSON is recommended for new integrations.How Xero API Authentication Works (OAuth 2.0 Explained)Since 2021, Xero requires OAuth 2.0 for all API access. The older OAuth 1.0a flow is fully deprecated.For server-side applications, use the standard Authorization Code flow.For mobile or single-page apps, use the Authorization Code flow with PKCE (Proof Key for Code Exchange).If you are building a native iOS app, see our detailed guide on Xero OAuth 2.0 integration with PKCE for iOS.OAuth 2.0 Flow Steps Register your app at developer.xero.com. Choose Web App or PKCE depending on your architecture. Redirect the user to the Xero authorization URL with your client_id, redirect_uri, requested scope, and a state parameter for CSRF protection. Exchange the authorization code for an access token and refresh token by calling the token endpoint. Use the access token in the Authorization: Bearer {token} header for all API calls. Refresh the token before it expires (tokens last 30 minutes). Use the refresh token to get a new access token without user interaction.ScopesXero uses granular scopes to control API access. Request only the scopes you need:ScopeAccessopenid profile emailUser identity (required for all apps)accounting.transactionsInvoices, bills, credit notes, payments, bank transactionsaccounting.contactsContacts, contact groupsaccounting.settingsChart of accounts, tax rates, currencies, tracking categoriesaccounting.reports.readProfit & Loss, Balance Sheet, Trial Balance, and other reportsaccounting.journals.readJournal entries (read-only)accounting.attachmentsFile attachments on transactionsassetsFixed assets APIprojectsProjects APIpayroll.*Payroll API (region-specific scopes)bankfeedsBankFeeds APIfilesFiles APIToken Management Best Practices Store refresh tokens securely encrypted at rest, never in client-side code or version control. Proactively refresh tokens 2–3 minutes before the 30-minute expiry to avoid failed API calls. Handle token revocation gracefully if a user disconnects your app from Xero, your refresh token becomes invalid. Detect 401 responses and prompt re-authorization. Use the offline_access scope to receive a refresh token. Without it, you only get a short-lived access token.Key Accounting API EndpointsThe Accounting API is the most-used Xero API. Here are the essential endpoints every integration needs to know:EndpointMethodsDescriptionPagination/InvoicesGET, POST, PUTSales invoices and bills. Filter by status, date, contact. Supports where parameter for OData-style filtering.Page-based (100 per page)/ContactsGET, POST, PUTCustomers, suppliers, and other contacts. Includes addresses, phone numbers, tax numbers.Page-based (100 per page)/PaymentsGET, POST, PUTPayments applied to invoices and bills. Link payments to bank accounts.Page-based (100 per page)/BankTransactionsGET, POST, PUTSpend and receive money transactions. Used for direct bank entries not tied to invoices.Page-based (100 per page)/JournalsGETSystem-generated journal entries. Read-only — you cannot create journals via this endpoint. See our guide on Xero Journal API limitations.Offset-based (100 per page)/ManualJournalsGET, POST, PUTUser-created manual journal entries. Supports multi-line journals with debit/credit pairs.Page-based (100 per page)/AccountsGET, POST, PUT, DELETEChart of accounts. Retrieve, create, or modify account codes. See our resource on downloading Xero chart of accounts.Not paginated (returns all)/ItemsGET, POST, PUT, DELETEInventory items and services. Includes purchase and sale unit prices.Not paginated/PurchaseOrdersGET, POST, PUTPurchase orders with line items, delivery addresses, and status tracking.Page-based (100 per page)/ReportsGETFinancial reports: Profit and Loss, Balance Sheet, Trial Balance, Budget Summary, and more. Note: some reports have known data extraction limitations.Not paginatedFiltering and QueryingMost list endpoints support the where parameter for OData-style filtering. Examples:GET /Invoices?where=Status=="AUTHORISED"&order=Date DESC GET /Contacts?where=Name.Contains("Acme") GET /BankTransactions?where=Date>=DateTime(2026,01,01) GET /Invoices?where=Contact.ContactID==guid("abc-123-def")Use the If-Modified-Since header to retrieve only records changed after a given timestamp essential for efficient sync workflows.Xero API Rate Limits (And How to Avoid 429 Errors)Xero enforces strict rate limits to protect platform stability.Understanding these limits is critical for production integrations, especially high-volume sync operations.For a deeper breakdown with practical examples, read our guide on Xero API sync limits and how many invoices you can sync per day.Limit TypeThresholdScopePer-minute limit60 API calls per minutePer tenant (organization)Daily limit5,000 API calls per dayPer tenantApp-wide limit10,000 calls per minuteAcross all tenants for your appHandling 429 (Too Many Requests) ErrorsWhen you hit a rate limit, Xero returns a 429 status code with a Retry-After header indicating how many seconds to wait. Your integration should: Implement exponential backoff: start with the Retry-After value, then double the wait time on subsequent 429s. Use batching: POST and PUT endpoints accept arrays of up to 50 items per request, reducing the number of API calls. Cache aggressively: store chart of accounts, tax rates, and other slowly-changing data locally. Use If-Modified-Since:for sync operations, only fetch records that changed since your last sync.Xero API Limitations (And Practical Workarounds)No API is perfect. Xero has several well-documented limitations that developers encounter in production. We have published detailed guides on each: Journal API is read-only: The /Journals endpoint does not support POST or PUT. You can only read system journals.To create journal entries, use /ManualJournals instead. Filtering voided and reversal entries is also problematic see our full breakdown: Xero Journal API Limitations: How to Filter Voided and Reversal Entries. Trial Balance data gaps: The Trial Balance report via API may omit tracking category breakdowns and has limited date-range filtering compared to the Xero UI. Read our guide: Overcoming Xero API Limitations for Trial Balance Data Extraction. Aging reports are not available via API: There is no direct endpoint for AR/AP aging reports. You must reconstruct aging data from invoice due dates and payment records. We detail the workaround here: Xero API Aging Reports: AR/AP Data Extraction Limitations. Cash flow reports not directly available: The Xero API does not expose a cash flow statement endpoint. You need to derive it from bank transactions and journal data. Our guide explains how: How to Generate Cash Flow Reports from Xero When the API Does Not Offer It. Bank reconciliation API gap: The API capabilities for reconciliation differ between Xero and competitors like QuickBooks. See our comparison: Xero Has It. QuickBooks Does Not. The API Gap You Must Know. Foreign currency bank revaluation: Calculating accurate balances for foreign currency accounts requires a custom approach. Our detailed guide: Bank Revaluation in Xero: Calculating Accurate Balances for Foreign Currency Accounts Using API.If you are running into these limitations on a production integration, our Xero integration service team has solved each of these problems at scale.WebhooksXero webhooks notify your application in real-time when data changes in a connected organization, eliminating the need for constant polling.Available Webhook Events Contacts: Create, Update Invoices: Create, Update Quotes: Create, Update Manual Journals: Create, Update Payments: Create, Update Credit Notes: Create, Update Purchase Orders: Create, UpdateWebhook SecurityEvery webhook subscription includes a signing key. Xero signs each payload with an HMAC-SHA256 hash using this key. Your endpoint must: Compute the HMAC-SHA256 of the raw request body using your signing key. Compare the computed hash with the x-xero-signature header value. Return 200 OK only if the signatures match. Return 401 otherwise. Respond within 5 seconds or Xero will retry (up to 7 retries with exponential backoff).Webhook Payload Format { "events": [ { "resourceUrl": "https://api.xero.com/api.xro/2.0/Invoices/a1b2c3d4", "resourceId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "eventDateUtc": "2026-03-15T10:30:00.000Z", "eventType": "Update", "eventCategory": "INVOICE", "tenantId": "your-tenant-id", "tenantType": "ORGANISATION" } ], "firstEventSequence": 1, "lastEventSequence": 1, "entropy": "random-string" }Important: The webhook payload only includes metadata (resource ID and type). You must make a follow-up API call to fetch the full resource data.SDKs & LibrariesXero maintains official SDKs for all major languages. These handle OAuth 2.0 token management, request signing, serialization, and pagination automatically.LanguagePackageGitHub RepositoryPythonxero-pythonXeroAPI/xero-pythonNode.jsxero-nodeXeroAPI/xero-node.NETXero.NetStandard.OAuth2XeroAPI/Xero-NetStandardJavaxero-javaXeroAPI/Xero-JavaRubyxero-rubyXeroAPI/xero-rubyGoxero-golangXeroAPI/xero-golangPHPxero-php-oauth2XeroAPI/xero-php-oauth2For integrations that connect multiple accounting platforms (Xero + QuickBooks + Sage), consider using a unified API abstraction layer rather than individual SDKs.Sandbox & TestingXero provides a Demo Company for every developer account a fully populated test organization with realistic sample data including invoices, contacts, bank accounts, and payroll records.Best Practices for Testing Use the Demo Company for initial development and automated testing. It resets periodically, so do not rely on specific record IDs persisting. Create a dedicated trial organization for integration testing that requires persistent data. Xero offers a free 30-day trial for each new org. Implement idempotent operations — use Xero-provided IDs (GUIDs) for updates rather than creating duplicate records. Test rate-limit handling by intentionally exceeding limits in your test environment and verifying your backoff logic works correctly. Test token expiration by forcing a token refresh and verifying your app handles the flow without user intervention. Validate webhook signatures using the Intent to Receive validation step during webhook subscription setup.Code ExamplesCreating an Invoice (Python)from xero_python.api_client import ApiClient from xero_python.accounting import AccountingApi, Invoice, Contact, LineItem from datetime import date # Assumes you have a configured api_client with valid OAuth 2.0 tokens accounting_api = AccountingApi(api_client) contact = Contact( name="Acme Corporation", email_address="billing@acme.com" ) line_item = LineItem( description="Web Development Services - March 2026", quantity=40.0, unit_amount=150.00, account_code="200", tax_type="OUTPUT" ) invoice = Invoice( type="ACCREC", contact=contact, line_items=[line_item], date=date(2026, 3, 15), due_date=date(2026, 4, 14), reference="INV-2026-0315", status="DRAFT" ) created_invoice = accounting_api.create_invoices( xero_tenant_id="your-tenant-id", invoices={"Invoices": [invoice]} ) print(f"Invoice created: {created_invoice.invoices[0].invoice_id}")Fetching Contacts with Pagination (Node.js)const { XeroClient } = require("xero-node"); async function getAllContacts(xero, tenantId) { let allContacts = []; let page = 1; let hasMore = true; while (hasMore) { const response = await xero.accountingApi.getContacts( tenantId, null, // ifModifiedSince null, // where null, // order null, // iDs page, // page number false // includeArchived ); const contacts = response.body.contacts; allContacts = allContacts.concat(contacts); // Xero returns 100 contacts per page hasMore = contacts.length === 100; page++; } console.log(`Fetched ${allContacts.length} contacts`); return allContacts; }OAuth 2.0 Token Refreshimport requests def refresh_xero_token(client_id, client_secret, refresh_token): """Refresh an expired Xero OAuth 2.0 access token.""" token_url = "https://identity.xero.com/connect/token" response = requests.post( token_url, data={ "grant_type": "refresh_token", "refresh_token": refresh_token, "client_id": client_id, "client_secret": client_secret, }, headers={"Content-Type": "application/x-www-form-urlencoded"} ) if response.status_code == 200: tokens = response.json() # tokens["access_token"] - new access token (valid 30 min) # tokens["refresh_token"] - new refresh token (store this!) # tokens["expires_in"] - seconds until expiry (1800) return tokens else: raise Exception(f"Token refresh failed: {response.text}")Frequently Asked QuestionsIs the Xero API free?Yes. Access to the Xero API itself is free. There are no per-call charges or separate API subscription fees. You only need a standard Xero subscription for each organization you connect to. Developer accounts and the Demo Company are also free. The cost is in your Xero subscription, not the API usage.How do I get Xero API credentials?Go to developer.xero.com and create a free developer account. Then create a new app from the “My Apps” dashboard. You will receive a Client ID and Client Secret. For PKCE-based apps (mobile, SPA), you only need the Client ID. Store your Client Secret securely and never expose it in client-side code.What are Xero API rate limits?Xero enforces three tiers of rate limiting: 60 calls per minute per tenant, 5,000 calls per day per tenant, and 10,000 calls per minute app-wide across all tenants. When you exceed a limit, the API returns a 429 Too Many Requests response with a Retry-After header. Use exponential backoff and batching to stay within limits.Can I access bank feeds through the Xero API?Yes, but only through the dedicated BankFeeds API, which is separate from the Accounting API. It allows you to push bank statement data into Xero for reconciliation. This API requires additional approval from Xero, and your app must meet their security requirements.How do I handle Xero OAuth 2.0 token expiration?Xero access tokens expire after 30 minutes. To maintain uninterrupted access, request the offline_access scope to receive a refresh token. Refresh tokens should be renewed proactively before expiry using the /connect/token endpoint. Always store the latest refresh token securely. If refresh fails, the user must re-authorize the app.Can I create invoices and other transactions via the API?Yes. The Accounting API supports full CRUD operations for invoices, bills, credit notes, payments, bank transactions, manual journals, contacts, and items. You can create records as DRAFT or AUTHORISED. Batch creation (up to 50 items per request) is also supported.How do I integrate Xero with other accounting platforms?You can either build separate integrations for each platform like QuickBooks or Sage, or use a unified integration layer that normalizes multiple accounting APIs. This approach simplifies multi-platform support and enables scalable automation.