Xero – Gotchas
Get paid sooner when you accept payments online straight from a Xero invoice. Give your customers different payment options, including PayPal and Stripe.
36 gotchas across 20 resources
These are connector-specific behaviors and limitations to be aware of when integrating.
Bank Feed Accounts2 gotchas
bankFeedAccountsAllBank Feed Accounts in Xero represent bank accounts connected to Xero's Bank Feeds feature, which automatically imports bank transactions into Xero on a daily basis. For more information on setting up and using Bank Feeds with Xero, see the Bank Feeds for Xero guide.
bankFeedAccountsAddBank Feed Accounts in Xero represent bank accounts connected to Xero's Bank Feeds feature, which automatically imports bank transactions into Xero on a daily basis. For more information on setting up and using Bank Feeds with Xero, see the Bank Feeds for Xero guide.
Bank Feed Statements1 gotcha
bankFeedStatementsAddIn Xero, if a transaction ID has been previously used, the API will reject the entire bank feed statement. However, if the statement includes multiple transactions and at least one transaction ID is unique, the API will return a success response but will not process the transactions with duplicate IDs.
Bill Payments1 gotcha
billPaymentsAddXero rejects bill payments where the organisation base currency, the bill currency, and the bank account currency are all different (a "3-currency transaction"):
"The invoice, payment and organisation base currencies are all different. Xero cannot currently handle a 3-currency transaction."
Pay from a bank account in either the base or the bill currency (two currencies) to avoid the error.
Bills2 gotchas
billsAddWhen providing both supplier.id and supplier.display_name (or supplier.name), Xero will update the contact's name if the ContactID exists. If the ContactID doesn't exist, Xero will automatically create a new contact using the provided name. This allows for automatic contact creation but may update existing contact names if the display_name differs from the stored name.
billsUpdateBills in xero can be tax inclusive, tax exclusive or no tax. The tax_inclusive field should be set to true, false or null if the bill is tax inclusive, tax exclusive or no tax.
Categories1 gotcha
categoriesAllXero limits to 10 attachments per resource (invoices/bills) of 3.5MB each.
Companies1 gotcha
companiesAllXero does not expose a total record count in list responses. meta.total_count will not appear.
Credit Notes4 gotchas
creditNotesAddFor adding invoice allocations, use the proxy API. Know more
creditNotesUpdate- Authorised Credit Note can only be updated to voided status and cannot be deleted once authorised.
- Any invoice allocation and payment must be deleted before updating the authorised Credit Note to status voided.
- To delete payments, use the payment ID from the raw data and delete it using the Delete Payment endpoint.
- For adding and removing invoice allocations, use the proxy API. Know more
creditNotesUpdateCredit notes in xero can be tax inclusive, tax exclusive or no tax. The tax_inclusive field should be set to true, false or null if the credit note is tax inclusive, tax exclusive or no tax.
creditNotesDeleteXero will only allow deleting a Credit Note if it is in draft status. Credit notes with "authorised" status cannot be deleted directly. Instead:
- If the credit note is empty (i.e., has no invoice allocations and payments), its status can be updated to "voided" by update Credit Note endpoint.
- If the credit note has invoice allocations and payments, these must be removed first. Once all invoice allocations and payments are deleted, the credit note's status can be updated to "Voided" by update Credit Note endpoint. Know more
Customers1 gotcha
customersAllIn Xero, contacts are not strictly separated into customers or suppliers until linked to a transaction. Apideck's approach is to return customers as IsCustomer=true||IsSupplier=false and suppliers as IsSupplier=true||IsCustomer=false. If you need other cases, use pass-through, e.g. ?pass_through[where]=IsCustomer=false AND IsSupplier=false for uncategorized contacts. See the Pass-through guide for more details.
Expenses3 gotchas
expensesAllIn Xero, both suppliers (also used for employees) and customers are identified using the same contact ID, which maps to both supplier_id and customer_id. To accurately determine the contact type, use the suppliers and customers endpoints.
expensesAddXero expenses map to Spend Money bank transactions and are created with status AUTHORISED — there is no draft state. Both a bank/credit-card account (account.id) and a supplier (supplier.id) are required, even though they are optional in the unified schema; omitting either returns a Xero validation error ("A Contact must be specified" / "The BankAccount must be specified"). The account.id must correspond to a 'bank' or 'credit_card' account type — other account types may result in errors or unexpected behavior. Xero's Expenses (expense-claims) module, where contact-less drafts can be saved in the UI, has no public write API. For a draft, payable-style expense without a bank account, use the Bills resource (Xero ACCPAY invoices) with status: "draft" instead.
expensesOneIn Xero, both suppliers (also used for employees) and customers are identified using the same contact ID, which maps to both supplier_id and customer_id. To accurately determine the contact type, use the suppliers and customers endpoints.
General Ledger Transactions2 gotchas
generalLedgerTransactionsAllBacked by Xero's Journals API, which moves to the Advanced app tier on 2026-03-02. Each posting (invoice, bill, payment, payroll, manual journal, etc.) appears here with its origin discriminated by source_type and source_id.
Filtering by source_type=journal_entry returns no results because Xero's underlying where=SourceType=="MANJOURNAL" filter does not match any rows on the Journals endpoint, even when manual-journal postings clearly exist in the unfiltered list response. To list manual-journal postings, use accounting/journal-entries?filter[scope]=manual or accounting/journal-entries directly after the manual-only redefinition.
currency and currency_rate are returned as null. Xero's Journals endpoint does not expose currency information; fetch the originating document via source_type + source_id to retrieve currency.
The Reference field on Xero Journals is mapped to reference. For manual-journal postings, this is the Narration of the originating manual journal.
generalLedgerTransactionsOneBacked by Xero's Journals/{id} endpoint. Two limitations of that endpoint surface here:
source_type and source_id are returned as null for every record retrieved through this single endpoint — invoices, bills, payments, manual journals, all of them. Xero's GET /api.xro/2.0/Journals/{id} strips those fields regardless of journal type. To recover them, query the list endpoint (GET /accounting/general-ledger-transactions) where these fields are populated. Note that filter[source_id] takes the originating document id (e.g., the invoice's id), not this resource's transaction id — use it when you already know the source document and want to find every GL posting it produced. To find a specific transaction by its own id, narrow the list with filter[updated_since] close to the transaction's posted_at and iterate.
currency and currency_rate are returned as null. Xero's Journals endpoint does not expose currency information; fetch the originating document via source_type + source_id (after recovering them through the list endpoint) to obtain currency.
Invoice Items1 gotcha
invoiceItemsAllXero limits to 10 attachments per resource (invoices/bills) of 3.5MB each.
Invoices4 gotchas
invoicesAllInvoices in Xero can be tax inclusive, tax exclusive, or no tax. The tax_inclusive field is set to true (tax inclusive), false (tax exclusive), or null (no tax). Line items include tax_rate references that link to the tax-rates resource.
invoicesAddWhen providing both customer.id and customer.display_name (or customer.name), Xero will update the contact's name if the ContactID exists. If the ContactID doesn't exist, Xero will automatically create a new contact using the provided name. This allows for automatic contact creation but may update existing contact names if the display_name differs from the stored name.
invoicesUpdateFor updating invoices, there are some limitations. Check out here
invoicesUpdateInvoices in Xero can be tax inclusive, tax exclusive, or no tax. Set tax_inclusive to true (amounts include tax), false (amounts exclude tax), or null (no tax applied). When updating, changing tax_inclusive recalculates all line item amounts.
Journal Entries3 gotchas
journalEntriesAllXero has two resources: Journals (postings from any source — invoices, bills, payments, manual journals, etc.) and ManualJournals (manual entries only). The default behaviour of journal-entries reads from Journals. From 2026-03-02 Xero moves the Journals endpoint to its Advanced tier; for the full general ledger view use accounting/general-ledger-transactions instead.
Use ?filter[scope]=manual to read exclusively from ManualJournals (free in every Xero tier, target behaviour after the upcoming default flip). Use ?filter[scope]=system or omit the filter to keep the legacy default. The ?filter[status]=draft shortcut continues to read drafts from ManualJournals.
The exposed id today depends on the journal entry status: for drafts it is the ManualJournalID; once posted, it is the Xero JournalID (different from the draft id). To track the underlying manual journal across the lifecycle, use the new manual_journal_id field returned in write responses (and use the pattern manual_journal_id ?? id for stable storage). On a draft → posted transition the id changes but the manual_journal_id remains stable.
Xero returns 100 items per call, so the limit parameter is ignored.
journalEntriesAddWrites go to Xero ManualJournals (free in every Xero tier).
When the resulting status is draft, the response id is the ManualJournalID. When the status is posted (either created posted directly, or transitioned from draft), the response id is the Xero JournalID for backwards compatibility — note that this is not the same value as the underlying ManualJournalID.
The response also returns manual_journal_id — the underlying ManualJournalID, which is stable across the entire lifecycle (draft → posted → voided). When persisting an id from this response, use the pattern manual_journal_id ?? id so your stored reference does not change on a draft → posted transition.
Xero only supports draft and posted for the status field. For ledger accounts in journal lines, you can use either ledger_account.id (UUID) or ledger_account.code; if only the ID is provided, the code will be resolved automatically.
Journal entries in xero can be tax inclusive, tax exclusive or no tax. The tax_inclusive field should be set to true, false or null if the journal entry is tax inclusive, tax exclusive or no tax.
journalEntriesUpdateXero has two resources: journals (entries from transactions like invoices, payments, or manual journals) and manual journals (user-created entries).
Draft entries are manual journals. When posted, they become journals and the ID changes. Updating a posted entry creates a reverse journal and a new one, so the ID also changes.
Xero only supports draft and posted for the status field.
For ledger accounts in journal lines, you can use either ledger_account.id (UUID) or ledger_account.code. If only the ID is provided, the code will be resolved automatically.
Ledger Accounts1 gotcha
ledgerAccountsAllXero limits to 10 attachments per resource (invoices/bills) of 3.5MB each.
Locations1 gotcha
locationsAllXero limits to 10 attachments per resource (invoices/bills) of 3.5MB each.
Payments2 gotchas
paymentsAddXero supports creating payments against multiple allocation types in a single request. However, the response will provide the ID of the first payment only. To access all payment IDs, inspect the raw data.
Xero rejects payments where the organisation base currency, the invoice currency, and the bank account currency are all different (a "3-currency transaction"):
"The invoice, payment and organisation base currencies are all different. Xero cannot currently handle a 3-currency transaction."
Pay from a bank account in either the base or the invoice currency (two currencies) to avoid the error.
paymentsUpdateXero doesn't support updating payments. To update a payment, you need to delete the existing payment and create a new one.
Purchase Orders3 gotchas
purchaseOrdersAddXero accepts a shipping address as a string instead of separate fields. Ie: 23 Main Street Central City Marineville 1234
purchaseOrdersUpdateXero accepts a shipping address as a string instead of separate fields. Ie: 23 Main Street Central City Marineville 1234
purchaseOrdersUpdatePurchase orders in xero can be tax inclusive, tax exclusive or no tax. The tax_inclusive field should be set to true, false or null if the purchase order is tax inclusive, tax exclusive or no tax.
Suppliers1 gotcha
suppliersAllIn Xero, contacts are not strictly separated into customers or suppliers until linked to a transaction. Apideck's approach is to return suppliers as IsSupplier=true||IsCustomer=false and customers as IsCustomer=true||IsSupplier=false. If you need other cases, use pass-through, e.g. ?pass_through[where]=IsCustomer=false AND IsSupplier=false for uncategorized contacts. See the Pass-through guide for more details.
Tax Rates1 gotcha
taxRatesAllXero tax rates include effective_tax_rate for the combined percentage. Tax types are categorized as INPUT (purchases), OUTPUT (sales), or NONE. Use the appropriate tax type when applying rates to invoices vs bills.
Tracking Categories1 gotcha
trackingCategoriesAllReturns tracking category options only. IDs and names are concatenated as "CategoryName - OptionName". The provided ID can be used to link to the correct tracking category to invoices, bills, etc.