Migration
The Migration Wizard imports data from other accounting providers into SpeyBooks. It orchestrates contact imports, opening balances, outstanding invoices, and bills through a state machine that validates each step before allowing execution.
This guide walks through a complete migration from start to finish. For the full endpoint reference, see Migration API.
Supported providers
| Provider | File format | Status |
|---|---|---|
| FreeAgent | .xlsx (multi-sheet export) | Supported - automated parsing |
| Xero | Manual CSV | Planned |
| QuickBooks | Manual CSV | Planned |
| Other | Manual CSV | Supported - manual attachment |
FreeAgent is fully automated: upload a single .xlsx export and the wizard parses sheets for contacts, trial balance, invoices, and bills. Other providers use the manual attachment workflow where you upload each data set separately through the standalone importers.
The state machine
Every migration moves through a defined set of states. The status is computed automatically based on which imports are attached and confirmed.
setup → files_attached → preview_ready → ready_to_execute → executing → completed
↘ abandoned
| Status | Meaning |
|---|---|
setup | Created, no files attached yet |
files_attached | At least one import linked |
preview_ready | All required imports present |
ready_to_execute | All validation gates passed |
executing | Execution in progress |
completed | Sealed - data imported (terminal) |
abandoned | Manually cancelled (terminal) |
Only one active (non-terminal) migration may exist per organisation at any time. This singleton constraint is enforced by a partial unique index. Attempting to create a second active migration returns 409 Conflict.
Starting a migration
Specify the source provider and a cutover date (the date from which SpeyBooks takes over). The cutover date determines how opening balances are calculated - all transactions in the source system up to this date are summarised into opening balances rather than imported individually.
The API validates that the organisation does not have an existing active migration, and that the organisation is not using the cash VAT scheme (which is not supported for migration).
Uploading provider data
FreeAgent (automated)
For FreeAgent migrations, upload the .xlsx export file to the upload-provider endpoint. The wizard automatically:
- Validates the file is .xlsx and under 10MB
- Parses the workbook into separate sheet buffers (contacts, trial balance, invoices, bills)
- Validates that required sheets are present
- Imports contacts through the Universal Contact Importer (UCI)
- Imports opening balances through the Opening Balance Confirmation Engine (OBCE)
- Stores invoice and bill sheets as deferred data for later processing
- Attaches the resulting import IDs to the migration
- Advances the migration status
The response includes a sheets array listing which sheets were found, and an uploads object showing the status of each import.
Deferred sheets
Invoices and bills are deferred because they depend on the opening balance being confirmed first. After reviewing and confirming the opening balance import, call POST /migrations/{id}/upload-deferred to process the invoice and bill sheets. This two-step approach ensures the clearing account reconciliation is correct before outstanding documents are imported.
Manual attachment
For other providers, import each data set separately using the standalone importers (contact import, opening balance import, invoice/bill import), then attach the resulting import IDs to the migration:
curl -X POST https://api.speybooks.com/v1/migrations/mig_1/upload \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{"contactsImportId": "dimp_5", "balancesImportId": "dimp_6"}'
All fields are optional - provide the ones you want to attach. Set a field to null to detach an import. The migration status is recomputed after each update.
Validation gates
Before a migration can be executed, it must pass a set of validation gates. Call the validate endpoint to run all checks and see which gates pass or fail.
Each gate returns a code, passed boolean, and human-readable message. Common gates include:
| Gate code | What it checks |
|---|---|
CONTACTS_CONFIRMED | Contact import is attached and confirmed |
BALANCES_CONFIRMED | Opening balance import is attached and confirmed |
RECONCILIATION_VALID | Imported debits equal credits |
If all gates pass and the migration is in an eligible status, the validate endpoint automatically advances it to ready_to_execute. If any gate fails, the response shows "ready": false with details on what needs fixing.
Executing the migration
Once validated, execute the migration to atomically import all data into SpeyBooks. This operation is irreversible - there is no undo.
On success, the migration advances to completed and returns a reconciliation proof showing total debits, total credits, and net (which should be zero for a balanced import).
The endpoint is idempotent: if the migration is already completed, it returns the existing reconciliation proof without re-executing. This makes it safe to retry on network failures.
On failure, the migration stays in ready_to_execute and returns the specific error. Fix the underlying issue and retry.
Reviewing the result
After execution, retrieve the full migration details to see per-import summaries. Each import shows row counts, created/skipped/error counts, and status.
The reconciliation object provides the mathematical proof that the import is balanced. A net of zero confirms that every debit has a matching credit.
Abandoning a migration
If you need to start over, abandon the active migration with DELETE /migrations/{id}. This sets the status to abandoned and releases the singleton constraint so you can create a new migration.
Only non-terminal, non-executing migrations can be abandoned. If the migration is already completed or currently executing, the endpoint returns 422.
curl -X DELETE https://api.speybooks.com/v1/migrations/mig_1 \
-H "Authorization: Bearer sk_live_..."
Migration history
List past migrations with GET /migrations/history. Returns up to 50 entries including completed and abandoned migrations, ordered by creation date descending.
Worked example: migrating from FreeAgent
Create the migration -
POST /migrationswith"provider": "freeagent"and your cutover dateExport from FreeAgent - in FreeAgent, go to Settings and export your data as .xlsx
Upload the export -
POST /migrations/mig_1/upload-providerwith the .xlsx file. The wizard parses contacts and trial balance automaticallyReview contacts - check the contact import via the standalone importer endpoints. Confirm when ready
Review opening balances - check the opening balance import. Confirm when ready
Process deferred sheets -
POST /migrations/mig_1/upload-deferredto import invoices and bills now that opening balances are confirmedValidate -
POST /migrations/mig_1/validateto run all gate checksExecute -
POST /migrations/mig_1/executeto import everything atomicallyVerify -
GET /migrations/mig_1to check the reconciliation proof and per-import summaries
Related endpoints
- Migration API - full endpoint reference
- CSV Import Guide - the standalone importers used by manual migrations
- Bank Reconciliation Guide - next step after migration