Nordlet

Docs / Guides

Migrating from another system

Bring a company's books over in one call — chart of accounts, partners, opening balances or full history, open invoices, fixed assets and stock.

When a company moves to Nordlet from another accounting program, its books do not start from zero. The historical-books importer takes everything the old system leaves you with and writes it in one call, one database transaction: if a single row fails a check, nothing is stored. The same package can be run through a check-only endpoint first, which performs every validation and rolls back.

POST /v1/migration/books/validate   # checks everything, writes nothing
POST /v1/migration/books/import     # writes everything or nothing

The web app has the same two buttons under Settings → Migration (paste the JSON or upload a file). The API key needs ledger:write plus the write scope of every section you include (partners:write, catalog:write, sales:write, purchases:write, assets:write, inventory:write). Both endpoints accept bodies up to 32 MB.

The cut-over date

Pick the cut-over date: the first day the company is run in Nordlet. Everything before it stays in the old system (or comes over as history, see below); everything from that day on is entered in Nordlet through the normal endpoints. All amounts in the package are "as of the day before the cut-over": the closing trial balance, the invoices still unpaid, the stock still on the shelf, the assets still on the register.

Before importing, do the following in Nordlet:

  1. Create the company and apply the chart of accounts template (ledger/accounts/apply-template) — or list the accounts you need in the package's accounts section.
  2. Create at least one warehouse (inventory/warehouses/create) if you import stock.
  3. Run the package through migration/books/validate until it reports no errors and you accept its warnings.

The package

Every section is optional; include the ones you have. All references between sections are by code (account code, partner code, item code, asset group code, warehouse code), and a code may refer either to a record that already exists in Nordlet or to one created by an earlier section of the same package.

{
  "cutoverDate": "2026-01-01",
  "source": "Rivilė GAMA",
  "accounts": [
    { "code": "6210", "name": "Advertising expenses", "type": "expense", "parentCode": "6" }
  ],
  "partners": [
    { "code": "301111222", "name": "UAB Klientas", "vatCode": "LT100001112223", "isCustomer": true },
    { "code": "302222333", "name": "UAB Tiekėjas", "isCustomer": false, "isSupplier": true }
  ],
  "items": [
    { "code": "SKU-1", "name": "Widget", "type": "product", "unit": "vnt", "vatRatePercent": "21" }
  ],
  "assetGroups": [
    { "code": "TRANSP", "name": "Vehicles", "assetAccountCode": "1230", "depreciationAccountCode": "1239",
      "defaultUsefulLifeMonths": 60 }
  ],
  "openingBalances": {
    "balancingAccountCode": "3410",
    "entries": [
      { "accountCode": "2710", "debit": "5000.00" },
      { "accountCode": "2410", "debit": "1500.00" },
      { "accountCode": "2040", "debit": "300.00" },
      { "accountCode": "1230", "debit": "12000.00" },
      { "accountCode": "1239", "credit": "2400.00" },
      { "accountCode": "4430", "credit": "700.00" },
      { "accountCode": "3010", "credit": "10000.00" }
    ]
  },
  "openReceivables": [
    { "partnerCode": "301111222", "number": "SF-1050", "issueDate": "2025-12-10", "dueDate": "2026-01-09",
      "grossTotal": "1210.00", "vatTotal": "210.00" },
    { "partnerCode": "301111222", "number": "SF-1049", "issueDate": "2025-11-20",
      "grossTotal": "500.00", "outstanding": "290.00" }
  ],
  "openPayables": [
    { "partnerCode": "302222333", "documentNumber": "T-77", "documentDate": "2025-12-15", "grossTotal": "700.00" }
  ],
  "fixedAssets": [
    { "groupCode": "TRANSP", "code": "CAR-1", "name": "Delivery van", "acquisitionDate": "2025-01-01",
      "acquisitionCost": "12000.00", "accumulatedDepreciation": "2400.00", "depreciatedMonths": 12 }
  ],
  "stock": [
    { "itemCode": "SKU-1", "quantity": "30", "unitCost": "10" }
  ]
}

Amounts are decimal strings, as everywhere in the API. source is a free label that ends up in the description of the opening-balance transaction and in the notes of migrated invoices.

accounts

Chart-of-accounts rows: code, name, type (asset, liability, equity, income, expense), optional parentCode and isPostable. Codes that already exist are left untouched and counted as existing. Use this when the old system's chart differs from the template, or skip it and apply the template first.

partners and items

Customers, suppliers and catalog items, keyed by code. In Lithuania the partner code is normally the company registry code; for private persons use any code that is unique within the company. Existing codes are skipped, not updated. Items accept type (product or service), unit, barcode, vatRatePercent, salePriceExclVat, purchasePriceExclVat.

openingBalances

The closing trial balance of the old system, one row per account: accountCode with either debit or credit. The rows are posted as one journal transaction dated openingBalances.date (defaults to the cut-over date) with document type migration_opening. Rows must reference postable accounts.

The transaction must balance. If the old system's trial balance does not — rounding, a profit that was never closed to equity, a missing account — give balancingAccountCode (typically retained earnings, 3410 in the Lithuanian template) and the difference is posted there; the response reports the amount as balancingAmount.

Only one opening-balance transaction may exist per company. A second import with openingBalances is refused (409). Corrections after the fact are ordinary manual journal transactions (ledger/journal/transactions/create); posted transactions are immutable, so the original stays as imported.

journal — full history instead of, or in addition to, balances

If you want the old system's transactions in Nordlet (so that reports for earlier periods work), send them here: one object per transaction with date, optional description and reference, and entries of accountCode + debit/credit. Each transaction must balance on its own. They are posted with document type migration.

Dates must be before the cut-over date. When the package also carries openingBalances, history must be dated on or after openingBalances.date — the usual pattern is balances at the start of the previous financial year plus that year's transactions, with the cut-over on the first day of the current year. Do not send balances and history for the same period: the balances would be counted twice.

Every month touched by the opening balances or the history must be an open accounting period (409 otherwise).

openReceivables and openPayables

Invoices that were still unpaid (fully or partly) at the cut-over. They are created as issued sale invoices and registered purchase invoices with paidAmount = grossTotal − outstanding, so bank matching (bank/transactions/match, bank/transactions/suggest-matches), the debt-aging report and payment webhooks work on them like on any other invoice.

  • outstanding defaults to grossTotal; vatTotal defaults to 0 (the net is derived).
  • dueDate defaults to the document date.
  • currency defaults to the company's invoice currency. For any non-EUR currency fxRate is required — units of that currency per 1 EUR, the same convention as the rest of the API — and it is stored on the invoice so that later payments compute the realized FX difference correctly.
  • Sale invoice number is the old system's full number and must be unique in the company. Purchase invoices are unique per partner and documentNumber.
  • The invoices are not posted again: their receivable/payable balance is already part of the opening balances. They are linked to the opening-balance transaction, which is why openPayables requires openingBalances in the same package.
  • Invoice lines are not imported; the invoice carries totals only. The old system remains the source for line detail and for VAT registers of pre-cut-over periods.

If the old system tracked customer balances without open items, create one invoice per partner for the balance (for example number OB-301111222) so it can be settled.

Numbering continues. When an imported sale invoice number has the form PREFIX-N and its issue date falls in the cut-over year, the number series for that prefix and year is set to N + 1 (never lowered). The response lists the resulting numberSeries; for other cases set the series yourself with reference/series/create ({ documentType: "sale_invoice", prefix, year, startAt }).

assetGroups and fixedAssets

Fixed assets come over with their depreciation to date: acquisitionCost, salvageValue, accumulatedDepreciation, depreciatedMonths and usefulLifeMonths (or the group's default). The straight-line run (assets/depreciation/post) continues from the remaining value over the remaining months. Assets whose accumulated depreciation already equals the depreciable value are created as fully_depreciated. Nothing is posted — the cost and accumulated depreciation are part of the opening balances.

Groups are keyed by code and need assetAccountCode and depreciationAccountCode (default expense account 6206).

stock

Stock on hand per item: itemCode, quantity, unitCost (cost per unit in EUR) and optionally warehouseCode (default: the default warehouse), lotNumber and expiryDate for lot- or serial-tracked items. Each row becomes an incoming stock movement dated on the cut-over date with document type migration and starts a FIFO layer at that cost. Nothing is posted — the stock value is part of the opening balances.

Checks and warnings

The validate endpoint fails (422) on missing accounts, non-postable accounts, unbalanced transactions, unknown partner/item/group/warehouse codes, duplicate codes or numbers inside the package, outstanding amounts above the gross, accumulated depreciation above the depreciable value, and missing FX rates. Numbers or codes that already exist in Nordlet give 409.

On top of that, both endpoints compare the sub-ledgers with the ledger entries in the same package and return the differences as warnings rather than errors:

  • total outstanding of openReceivables vs the balance on the receivables account (sales.receivable posting rule, 2410 by default);
  • total outstanding of openPayables vs the payables account (purchases.payables, 4430);
  • total cost of stock vs the stock account (inventory.stock, 2040);
  • per asset group, the assets' cost vs the group's asset account and their accumulated depreciation vs the depreciation account.

A warning means the old system's sub-ledger and general ledger disagree. Fix the package (or accept the difference knowingly) before importing.

The response

{
  "dryRun": false,
  "cutoverDate": "2026-01-01",
  "accounts": { "created": 1, "existing": 0 },
  "partners": { "created": 2, "existing": 0 },
  "items": { "created": 1, "existing": 0 },
  "assetGroups": { "created": 1, "existing": 0 },
  "openingBalances": { "journalTransactionId": "…", "date": "2026-01-01", "entries": 8,
                        "debitTotal": "18800.00", "creditTotal": "18800.00", "balancingAmount": "5700.00" },
  "journal": { "transactions": 0, "entries": 0 },
  "openReceivables": { "created": 2, "outstandingTotal": "1500.00" },
  "openPayables": { "created": 1, "outstandingTotal": "700.00" },
  "fixedAssets": { "created": 1, "costTotal": "12000.00", "accumulatedDepreciationTotal": "2400.00" },
  "stock": { "movements": 1, "costTotal": "300.00" },
  "numberSeries": [],
  "warnings": []
}

dryRun: true marks a validate run; there journalTransactionId is null. An import is recorded in the audit log as migration.imported with the same summary.

After the import

  • Lock the periods before the cut-over (ledger/periods/lock) so nothing is posted into migrated history by accident.
  • Check the trial balance and the balance sheet as of the cut-over date against the old system's closing reports.
  • Continue with the normal flow: issue invoices, import bank statements, match payments — the migrated open invoices settle like any other.

What the importer does not cover: invoice lines and VAT registers of past periods (keep them in the old system or its archive), payroll history, and documents other than invoices (orders, waybills, contracts).