Xero
With Xero, Vodia Billing computes the tax and Xero records the figures supplied to it.
This is not a preference. Xero's automated sales tax cannot run on invoices created through its API, so an API-created invoice carries whatever tax figures the caller provides. Vodia Billing therefore has to be the tax authority for a Xero tenant, and the tenant needs a tax profile applied here.

Setup
1. Register the app
In the Xero developer portal, create an app of type Web app.
Redirect URI:
https://your-domain/api/xero/callback
Copy the client ID and client secret.
2. Enter the credentials
Accounting → App, choose Xero, paste the client ID and secret. Both are
stored encrypted under ENCRYPTION_KEY.
3. Connect the organisation
Accounting → Connect. Authorise the specific Xero organisation you bill from. The organisation name appears on the connection once it is established.
4. Discover
Discover pulls the contact list, chart of accounts and tax rates.
5. Map accounts
Map usage, recurring, goods and discount lines to revenue account codes from your Xero chart of accounts.
6. Map customers
Map each billing tenant to a Xero contact. Auto-bind proposes matches from account references and domain names; confirm each one.
7. Apply a tax profile
Every Xero tenant needs a tax profile applied in Tax, because Vodia Billing is computing the tax that Xero will record.
Tax types
Xero invoice lines carry a TaxType. Vodia Billing supplies the tax type
alongside the amount it computed.
Two failure modes here produce a 0.00 tax figure with no error at all:
TaxType: "AVALARA"returns a successful response with zero tax.- GUID-shaped tax type values are Avalara-managed and cannot be used on a manual tax document.
Both look like success. Always check the tax figure on the first invoice pushed to a new Xero organisation, in Xero itself, before you send anything to a customer.
Use the tax rates that Discover pulled from your own organisation. Those are the ones that work.
Totals
For a Xero tenant, Vodia Billing's total is the invoice total. There is no separate face value to reconcile, because Xero is recording what it was given rather than computing its own figure.
The divergence detector treats Xero this way, so a mismatch on a Xero tenant is a real problem and worth investigating, unlike on QuickBooks.
One push, and no re-push button
Vodia Billing creates Xero invoices with PUT, not POST, and this is
deliberate.
Xero's POST /Invoices behaves as an upsert. Sent against an invoice number
that already exists — including one already authorised — it overwrites it
without error.
PUT is create-only. A second attempt against the same number returns a 400
rather than destroying the original.
On top of that, Vodia Billing refuses a second push locally, before any API
call, once an invoice is recorded as pushed. Between the local blocker and
PUT, a duplicate push cannot happen.
The consequence is that there is no re-push button. If a push genuinely needs redoing, the invoice must be voided in both systems and reissued.
Daily API ceiling
Xero's daily limit is 1,000 calls per organisation, not 5,000.
A pushed tenant costs roughly three calls, so a full fleet push is limited to around 300 tenants per organisation per day. Bill runs pace themselves against this and stop cleanly when the budget is spent, reporting what remains for the next day.
If you bill more than a few hundred tenants from one Xero organisation, split the fleet across multiple runs on consecutive days, or across multiple organisations.
Sending the invoice
For a Xero tenant, either side can send. Configure it per tenant:
- Vodia Billing sends — the customer receives the Vodia Billing invoice email, with call detail attached and a share link. Xero holds the accounting record.
- Xero sends — Xero emails its own invoice. Vodia Billing then treats the invoice as externally delivered and does not send its own copy.
Reminders follow the same setting. This is why tax authority and delivery are tracked separately: a Xero tenant has Vodia Billing computing tax while either side may be sending.
Credits
Because Vodia Billing owns the tax on a Xero tenant, credits can be issued from within Vodia Billing. They apply after tax, and voiding the invoice restores them.
The corresponding credit note still has to be raised in Xero. Vodia Billing records the credit but does not create Xero credit notes.
Australian GST
The discount travels to Xero as its own negative line, so GST should be computed on the discounted base.
Verify that figure by hand in Xero on the first Australian invoice that carries a discount, before it reaches the customer. Tax is the one area where a wrong result produces an invoice that looks entirely normal.
Worked example
What arrives in Xero
The invoice from Worked Example, pushed to Xero:
Invoice INV-00151 Contact: Acme Corp
Date 2026-09-01 Due 2026-09-15 Status: AUTHORISED
Line Qty Unit Account TaxType Amount
US National 1240.00 0.012 200 OUTPUT2 14.88
United Kingdom 86.00 0.035 200 OUTPUT2 3.01
UK Mobile 12.00 0.14 200 OUTPUT2 1.68
US Toll Free 318.00 0.00 200 OUTPUT2 0.00
Extensions 42 12.00 200 OUTPUT2 504.00
Hunt groups 3 5.00 200 OUTPUT2 15.00
DID numbers 18 1.50 200 OUTPUT2 27.00
Managed support plan 1 150.00 200 OUTPUT2 150.00
Desk handset 1 89.00 260 OUTPUT2 89.00
Subtotal 804.57
Total Tax 82.72
TOTAL 887.29
Xero's total is 887.29, the same as Vodia Billing's, because Xero is
recording the tax figures it was given rather than computing its own.
That is why a divergence alert on a Xero tenant is a real problem worth investigating, unlike on QuickBooks.
The failure that looks like success
The same push with the wrong tax type:
Line Qty Unit Account TaxType Amount
US National 1240.00 0.012 200 AVALARA 14.88
...
Subtotal 804.57
Total Tax 0.00 <-
TOTAL 804.57
HTTP 200. No error, no warning, no alert. An invoice that is $82.72 short, sitting in Xero as authorised.
AVALARA and GUID-shaped tax type values are Avalara-managed and cannot
carry manual tax. Use the tax rates Discover pulled from your own
organisation.
Check the tax figure on the first invoice pushed to any new Xero organisation, in Xero, before it goes to a customer.
A second push
POST /api/invoices/<id>/push
409 Conflict — invoice already pushed to Xero on 2026-09-01 03:14
Refused locally, before any API call. And if it somehow reached Xero, the
adapter uses PUT:
PUT /Invoices (InvoiceNumber INV-00151)
400 Bad Request — invoice number already exists
POST /Invoices would have behaved as an upsert and overwritten the
authorised invoice without error. PUT is create-only. Between the local
blocker and PUT, a duplicate push cannot happen — and that is why there is
no re-push button.
Pacing against the daily ceiling
Push run, Xero org "Northwind Telecom"
Ceiling: 1,000 calls/day
Consumed: 996
Pushed: 331 tenants (~3 calls each)
Remaining: 68 tenants — resume tomorrow
At about three calls per tenant, a single Xero organisation supports roughly 330 pushed tenants per day.