Skip to content

Migrate from Xero / MYOB / QuickBooks

Bringing an existing set of books across to SAE Books. The mechanics are the same regardless of the source system — only the export format differs.

Cutover date

Pick a clean cutover. End-of-quarter (after BAS) is best; end-of-month is fine. Mid-period migration works but doubles the reconciliation work because you're crossing two systems' GST accruals.

What you migrate vs. what you reset

Migrate Reset (start fresh in SAE Books)
Chart of accounts Bank-feed connections (re-consent in SAE Books)
Customers / vendors / contacts User accounts (recreate via signup or invite)
Open invoices and bills API integrations (re-issue tokens)
Open credit notes
Asset register
Trial balance as at cutover

You do not migrate historical transactions. They stay in the incumbent system as a read-only archive — Australian tax law requires five years of retention regardless of which platform you're running today.

1. Export from the incumbent

Source Export endpoint / report
Xero Reports → Export, plus Reports → Trial Balance for the cutover snapshot
MYOB File → Export Data
QuickBooks Online Reports → Export to Excel; Lists → Customer/Vendor → Export

Get four CSVs at minimum:

  • Chart of accounts (code, name, type, parent_code)
  • Customers (name, abn, email, billing_address, …)
  • Vendors (same shape, separate file)
  • Trial balance as at cutover date

Plus, if you carry assets:

  • Asset register (required columns: code, name, purchase_date, cost, depreciation_model_id, cost_account_code, accum_dep_account_code)

2. Transform

SAE Books provides a QBO CSV import parser (saebooks/services/imports/qbo.py) that handles QBO's standard "Customer.csv" / "Vendor.csv" and Account List CSV exports, mapping them to SAE Books contacts and chart of accounts. For Xero and MYOB exports, you'll need to normalise the CSV columns to match the SAE Books expected headers before upload — the column names vary by source, but the CoA wizard accepts code, name, type, parent_code and the contact wizard accepts name, email, phone, abn as core columns.

3. Import via the wizard API

All imports go through the wizard API at /api/v1/imports/wizards. There is no single-step bulk endpoint that returns a job_id — the wizard is a multi-step flow: start, upload, preview, commit.

# 1. Start a wizard (kind: "coa" for chart of accounts, "qbo" for QBO contacts/CoA)
curl -X POST "$SAEBOOKS_API_BASE/imports/wizards" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"kind": "coa"}'
# → {"wizard_id": "...", "step": 0, "state": {}}

# 2. Upload the CSV (step patch)
curl -X POST "$SAEBOOKS_API_BASE/imports/wizards/$WIZARD_ID/step" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d "{\"step\": 0, \"patch\": {\"raw\": \"$(cat accounts.csv | python3 -c 'import sys,json; print(json.dumps(sys.stdin.read()))')\"}}"

# 3. Commit
curl -X POST "$SAEBOOKS_API_BASE/imports/wizards/$WIZARD_ID/commit" \
  -H "Authorization: Bearer $JWT"
# → {"new": N, "changed": M, "archived": K}

Available kind values: bank_csv, bank_ofx, coa, bill_csv (community tier); qbo (Pro+ — requires FLAG_QBO_IMPORT).

The asset register import uses the web UI (Settings → Assets → Import CSV) rather than the JSON API — the flow is a two-step preview-then-apply form submission. Use the UI for asset migrations.

4. Opening balances

The trial-balance snapshot from your incumbent becomes a single opening-balances journal in SAE Books — same pattern as a first chart of accounts. The journal date is the day after your cutover (so the incumbent owns up to and including the cutover date, SAE Books owns from the next day).

5. Open AR / AP

Open invoices and bills are re-entered as live documents, not history. There is no bulk JSON endpoint for invoices — create them individually via POST /api/v1/invoices (one per open invoice), or use the bill_csv import wizard for bills:

# Individual invoice
curl -X POST "$SAEBOOKS_API_BASE/invoices" \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d @invoice.json

# Bills in bulk — use the bill_csv wizard (kind: "bill_csv")
# Bills are imported as DRAFTS for human review before posting.

Each invoice posts the same journal it would have if you'd raised it natively, minus any payments — those land via the bank feed once you reconnect.

6. Reconcile

Run the trial balance in both systems and compare. They should agree to the cent. Any difference is one of:

  • A miscoded transformation (foreign currency line, GST flag, account-type confusion) — fix the script and re-run
  • An incumbent-system quirk (Xero's "system accounts", MYOB's job-cost accruals) — usually resolved by re-mapping to the closest SAE Books equivalent
  • A genuinely missing entry — post it manually as an opening-balance adjustment, with a clear narration

7. Cut over

Stop posting in the incumbent. Direct bank feeds, integrations, and users at SAE Books. Keep the incumbent in read-only archive mode for the legal retention period.

Next