CSV Import
Import bank statements, contacts, invoices, bills, and opening balances into SpeyBooks in a controlled, auditable way.
SpeyBooks has a dedicated import pipeline for each data type. Every import follows the same pattern: upload, preview, resolve conflicts, confirm. No data is committed until you explicitly confirm.
Supported imports
| Data type | Endpoint | Pipeline |
|---|---|---|
| Bank transactions | POST /v1/bank-imports/upload | Upload, preview, remap columns, confirm |
| Contacts | POST /v1/contact-imports/upload | Upload, preview, resolve conflicts, confirm |
| Opening balances | POST /v1/opening-balances/upload | Upload, map accounts, verify balance, confirm |
| Invoices | POST /v1/invoice-imports/upload | Upload, preview, confirm (generates ledger entries) |
| Bills | POST /v1/bill-imports/upload | Upload, preview, confirm (generates ledger entries) |
All five are live and available via API.
Design principles
Every import pipeline enforces the same guarantees:
- Preview before commit - uploaded data enters a preview state where you can inspect, remap, and resolve conflicts before anything touches the ledger
- All-or-nothing - if confirmation fails, no partial data is written
- Auditable - every import is tracked with row counts, conflict resolution, and timestamps
- Reversible - opening balance, invoice, and bill imports can be voided, which creates reversal journal entries
- No implicit mutations - imported data enters through the same validation as API-created data
Bank statement import
Universal Ingestion Engine
Bank imports use the Universal Ingestion Engine (UIE), which infers the CSV schema automatically. You don't need to specify a format or select a bank from a dropdown - upload any bank's CSV and the engine detects the columns.
The UIE performs column detection (date, description, amount, balance, reference), balance proof verification (opening + movements = closing), duplicate detection against previously completed imports, and row conservation (every row is accounted for).
UK bank presets are available for NatWest, Starling, Monzo, HSBC, Lloyds, Tide, and others. These are verification hints, not requirements - the engine works with any CSV.
Upload
curl -X POST https://api.speybooks.com/v1/bank-imports/upload \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: multipart/form-data" \
-F "file=@natwest-jan-2026.csv" \
-F "bankAccountId=acc_1200"
The response includes a balance proof showing whether the statement balances internally.
Smart Map (column remap)
If the engine's column detection needs correction, use the remap endpoint:
curl -X POST https://api.speybooks.com/v1/bank-imports/bi_1/remap \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"mappings": {
"date": "Transaction Date",
"description": "Details",
"amount": "Amount"
}
}'
Confirm
Once you're satisfied with the preview, confirm the import:
curl -X POST https://api.speybooks.com/v1/bank-imports/bi_1/confirm \
-H "Authorization: Bearer sk_live_..."
Confirmed bank transactions are external evidence, not ledger entries. They must be matched or categorised during bank reconciliation to create accounting entries.
Contact import
Contact imports support identity resolution and duplicate detection. The pipeline detects potential conflicts when an imported contact matches an existing one by name or email.
Upload
curl -X POST https://api.speybooks.com/v1/contact-imports/upload \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: multipart/form-data" \
-F "file=@contacts.csv"
Resolve conflicts
If the preview shows conflicts (e.g. an imported "Acme Corp" matches an existing "Acme Corporation"), resolve them before confirming:
curl -X POST https://api.speybooks.com/v1/contact-imports/ci_1/resolve \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"resolutions": [
{ "row": 3, "action": "merge", "existingContactId": "cont_12" },
{ "row": 7, "action": "create_new" }
]
}'
Confirm
curl -X POST https://api.speybooks.com/v1/contact-imports/ci_1/confirm \
-H "Authorization: Bearer sk_live_..."
Contacts imported via CSV behave identically to those created via the API.
Opening balances
Opening balance import is how you bring your existing accounting position into SpeyBooks. Upload a trial balance CSV, map accounts to the SpeyBooks chart of accounts, and the system enforces that total debits equal total credits before committing.
Upload
curl -X POST https://api.speybooks.com/v1/opening-balances/upload \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: multipart/form-data" \
-F "file=@trial-balance.csv"
Map accounts
If imported account names don't match the SpeyBooks chart, map them:
curl -X PATCH https://api.speybooks.com/v1/opening-balances/ob_1/map \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"mappings": [
{ "importedAccount": "Trade Debtors", "accountId": "acc_1100" },
{ "importedAccount": "Trade Creditors", "accountId": "acc_2100" }
]
}'
Trial balance gate
The confirm endpoint enforces a hard gate: total debits must equal total credits. If the trial balance doesn't balance, confirmation is rejected with a clear error showing the difference. This prevents starting your books in an inconsistent state.
Confirm
curl -X POST https://api.speybooks.com/v1/opening-balances/ob_1/confirm \
-H "Authorization: Bearer sk_live_..."
Void
If you need to redo opening balances (e.g. after discovering an error in the source data), void the import. This creates reversal journal entries:
curl -X POST https://api.speybooks.com/v1/opening-balances/ob_1/void \
-H "Authorization: Bearer sk_live_..."
Invoice and bill imports
Invoice and bill imports generate ledger entries on confirmation. Invoices create AR (accounts receivable) entries, bills create AP (accounts payable) entries. Both support voiding with automatic reversal journals.
Upload invoices
curl -X POST https://api.speybooks.com/v1/invoice-imports/upload \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: multipart/form-data" \
-F "file=@invoices.csv"
Upload bills
curl -X POST https://api.speybooks.com/v1/bill-imports/upload \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: multipart/form-data" \
-F "file=@bills.csv"
Both follow the same preview-then-confirm pattern. On confirmation, contra-wash clearing proof verifies that the imported totals reconcile with the ledger entries created.
Migration wizard
For a complete migration from another provider (Xero, FreeAgent, or QuickBooks), use the Migration Wizard instead of individual import endpoints. It orchestrates all five import types in sequence with mathematical verification at each phase. See the features page for details.
General CSV rules
All CSV imports accept:
- UTF-8 encoded files
- Header row required
- Comma-separated values
- One record per row
- No formulas or macros
Invalid rows are rejected with structured errors including row numbers and field-level messages. Partial imports are never committed.
Error codes
| Code | HTTP | Description |
|---|---|---|
validation_error | 400 | CSV format invalid or row validation failed |
balance_mismatch | 422 | Opening balance debits do not equal credits |
unresolved_conflicts | 422 | Contact import has unresolved conflicts |
file_too_large | 413 | CSV exceeds maximum file size |
not_found | 404 | Import does not exist |
Related endpoints
- Bank Imports API - full bank import reference
- Contact Imports API - contact import pipeline
- Opening Balances API - trial balance import
- Invoice Imports API - invoice bulk import
- Bill Imports API - bill bulk import
- Migration Wizard API - orchestrated provider migration
- Bank Reconciliation - what happens after bank import