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 typeEndpointPipeline
Bank transactionsPOST /v1/bank-imports/uploadUpload, preview, remap columns, confirm
ContactsPOST /v1/contact-imports/uploadUpload, preview, resolve conflicts, confirm
Opening balancesPOST /v1/opening-balances/uploadUpload, map accounts, verify balance, confirm
InvoicesPOST /v1/invoice-imports/uploadUpload, preview, confirm (generates ledger entries)
BillsPOST /v1/bill-imports/uploadUpload, 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

CodeHTTPDescription
validation_error400CSV format invalid or row validation failed
balance_mismatch422Opening balance debits do not equal credits
unresolved_conflicts422Contact import has unresolved conflicts
file_too_large413CSV exceeds maximum file size
not_found404Import does not exist

Related endpoints