Nordlet

Dokumentation / Leitfäden

Bank & payment imports

camt.053 statements, Stripe CSV, PSP settlement reconciliation and custom JSON feeds.

All imports land as bank transactions on a bank account (bank/accounts/create). Transactions carry an externalId; re-importing the same file is safe — rows whose (bankAccountId, externalId) already exist are skipped and reported in the skipped counter. Imported transactions start in status new and are posted to the ledger only when matched (bank/transactions/match, with bank/transactions/suggest-matches for candidates).

The web app exposes the same imports under Bank → Import file (file upload, template selector).

Import templates

An import template names a data source, lists the fields the source delivers, and maps the fields that carry money to ledger accounts. Templates are managed under Bank → Import templates and via bank/import-templates/create|update|delete|get|list.

POST /v1/bank/import-templates/create
{
  "name": "Stripe payments export",
  "type": "stripe",
  "fields": [
    { "name": "id" },
    { "name": "Created date (UTC)" },
    { "name": "Amount" },
    { "name": "Fee", "accountCode": "6800" },
    { "name": "Amount Refunded" }
  ],
  "metaFields": ["order_id"]
}
  • type is one of stripe, iso20022 or bank_connection.
  • Each field has a name and an optional accountCode. Only the fields that carry money accept an account: Stripe Amount, Fee and Amount Refunded; camt.053 Ntry/Amt; bank feed transaction_amount.amount. The account must exist in the chart of accounts (create it with ledger/accounts/create).
  • fields may be omitted; it then defaults to the standard field list of the type: the column names of the Stripe Payments → Export file (without the 19 Card … columns, which the form also leaves unticked by default), the camt.053 elements every statement carries (Stmt/Id, Stmt/Acct/Id/IBAN, Stmt/Bal/…, Ntry/Amt, Ntry/CdtDbtInd, Ntry/Sts, Ntry/BookgDt), or the attributes a PSD2 bank feed returns per transaction. Add or remove entries to match your file: the importer also reads the optional camt.053 elements Ntry/ValDt, Ntry/NtryRef, Ntry/AcctSvcrRef, Ntry/AddtlNtryInf, TxDtls/Refs/EndToEndId, TxDtls/Refs/TxId, TxDtls/RltdPties/Dbtr/Nm, TxDtls/RltdPties/DbtrAcct/Id/IBAN, TxDtls/RltdPties/Cdtr/Nm, TxDtls/RltdPties/CdtrAcct/Id/IBAN, TxDtls/RmtInf/Ustrd and TxDtls/AddtlTxInf.
  • A Stripe Customer Email field may carry "createPartner": true. The import then creates a customer (partner of type person, flagged as customer) for every email in the file that no existing partner has, using Customer Description as the name (the email when it is empty) and the Card Address … columns as the address when the file contains them. Existing partners are matched by email, case-insensitively. The response reports the count in customersCreated.
  • metaFields (Stripe only) lists the metadata keys your charges carry. Stripe exports each key as a <key> (metadata) column; the list is free-form and can be long.
  • invoiceItemId (Stripe only, optional) is a catalog item. When set, every invoice and credit note line the import creates references that item (its posting and, when invoiceVatRatePercent is empty, its VAT rate); the line text still comes from the charge.
  • companyMetaField (Stripe only) names one of those keys as the company flag. When a charge carries true, 1, yes, company, business or b2b in that column, the customer created for it is a partner of type company instead of person.
  • invoiceMetaField (Stripe only) names one of those keys, for example an order number, and invoiceVatRatePercent the VAT rate for invoices created from it. On import every charge and refund row is grouped by the value of that key:
    • a value that matches an existing sale invoice — a sales order whose externalRef is that value (ecommerce/orders/create, any channel) or an invoice whose fullNumber is that value — links the group to that invoice;
    • a value with no invoice creates one: partner from Customer Email (created when missing, as for createPartner), one line per charge (Checkout Line Item Summary or Description as the text, the charge amount as a VAT-inclusive price at invoiceVatRatePercent), issue date = the first charge date, VAT treatment from the company and the card country. The invoice is issued and posted at once, and a sales order with channel stripe and that value as externalRef is recorded so later imports link instead of creating;
    • each charge in the group is then matched to the invoice as a payment (posting the receipt); each refund creates an issued credit note against the invoice for the refunded amount and matches the refund to it. Counts come back as invoicesCreated, invoicesLinked, creditNotesCreated and paymentsMatched; groups that could not be processed (no customer email, mixed currencies, a linked invoice that is still a draft or already fully paid) are listed in warnings and left as new bank transactions. Do not also map Amount to a ledger account in the same template: a charge matched to an invoice is not posted a second time, but an unmatched one would be.
  • Template names are unique per company.
  • camt.053 and bank-connection templates carry the same options, read from the statement instead of Stripe columns:
    • Create missing partners: createPartner on the debtor name row (TxDtls/RltdPties/Dbtr/Nm, feed debtor.name) creates a customer for every unknown payer of incoming money; on the creditor name row (TxDtls/RltdPties/Cdtr/Nm, feed creditor.name) a supplier for every unknown payee of outgoing money. Partners are recognised by IBAN (stored in partner_bank_accounts), then by exact name; a new partner is a company when the name carries a legal form (UAB, MB, SIA, GmbH, Ltd, …), otherwise a person. The count comes back as customersCreated (file import) or partnersCreated (feed sync).
    • Link invoices by reference: invoiceMetaField set to a remittance or reference row (TxDtls/RmtInf/Ustrd, TxDtls/RmtInf/Strd/CdtrRefInf/Ref, TxDtls/Refs/EndToEndId; feed remittance_information, reference_number) matches every new bank line whose reference, remittance or description contains the number of an open issued sale invoice (money in) or registered purchase invoice (money out), and posts the payment. With advanceInvoices: true an unmatched incoming payment from a known partner creates an issued advance invoice (type advance) for the amount at invoiceVatRatePercent or the invoiceItemId rate and matches the payment to it; the partner must be known, so tick the debtor row too.
    • Posting rules by transaction code: besides Ntry/Amt / transaction_amount.amount, the field list has rule rows Ntry/BkTxCd=CHRG, =INTR, =TAXE, Ntry/CdtDbtInd=DBIT, =CRDT (feed: bank_transaction_code=…, credit_debit_indicator=…). A line is posted to the account of the first rule it matches: its bank transaction code family, then its direction, then the plain amount row. Rule rows are not checked against the file.
    • Bank charges partner: createPartner on the …=CHRG rule row creates a supplier named after the bank (statement Stmt/Acct/Svcr/FinInstnId/Nm, or the connection's bank, or the ledger bank account name) and books every charge with that partner.
    • Bank-connection templates are chosen per feed account with bank/feeds/accounts/configure (Bank → Connections in the app), which also sets syncSchedule (manual, daily, weekly, monthly): the API syncs accounts with a schedule by itself once their period has passed. Templates are applied on every bank/feeds/sync; the sync response then also reports posted, partnersCreated, invoicesLinked, invoicesCreated, paymentsMatched and warnings.

Pass templateId to bank/statements/import instead of format: the template's type selects the parser, and the file must contain every field listed in the template — a file missing any of them is rejected with HTTP 422 and the missing column or element names. Metadata fields are not required to be present. Bank-connection templates describe a live PSD2 feed and cannot be used for file import.

Every movement produced from a field that has an accountCode is posted at import time as one journal transaction: money in debits the bank account's ledger account and credits the mapped account; money out debits the mapped account and credits the bank account's ledger account. Amounts are converted to the base currency at the movement date. The bank transaction is then matched to that journal transaction (matchedDocumentType = journal_transaction) and no longer offered for invoice matching. The response reports the count in posted. Movements from unmapped fields stay new and are matched to invoices as before — for example map Fee to the PSP fee expense account and leave Amount unmapped so charges are still matched to sale invoices.

POST /v1/bank/statements/import
{
  "bankAccountId": "…",
  "templateId": "…",
  "content": "id,Created date (UTC),Amount,…"
}

ISO 20022 camt.053 (open banking XML)

Lithuanian banks (Swedbank, SEB, Luminor, Šiaulių bankas, …) let you download account statements as ISO 20022 camt.053 XML from their internet banking — usually under statement export/download, format "ISO XML" or "camt.053". The same format is returned by PSD2 account-information APIs.

POST /v1/bank/statements/import
{
  "bankAccountId": "…",
  "format": "camt053",
  "content": "<?xml version=\"1.0\"…>…"
}
  • A file may contain several statements; each statement's IBAN must match the bank account's IBAN (statements without an IBAN, or accounts without one, are accepted as-is).
  • Entry references (AcctSvcrRef/NtryRef, falling back to a content hash) become externalId.
  • The response echoes per-statement metadata: statement id, period, opening/closing balances, transaction count.
  • Counterparty name, IBAN and payment purpose are extracted per entry for matching.

Payments in the other direction are exported as pain.001 via bank/payments/export.

Stripe CSV

Export from the Stripe dashboard — either report works:

  • Reports → Balance → Itemized balance change from activity (recommended): columns balance_transaction_id, created_utc, currency, gross, fee, net, reporting_category, description.
  • Payments → Export: columns id, Created (UTC), Amount, Fee, Currency, Description, Customer Description.
POST /v1/bank/statements/import
{
  "bankAccountId": "…",
  "format": "stripe-csv",
  "content": "balance_transaction_id,created_utc,currency,gross,fee,net,…"
}

Create a dedicated bank account for Stripe (e.g. name "Stripe", GL account 2710 or its own subaccount) — the Stripe balance behaves like a bank account: charges flow in, fees are deducted, payouts transfer the balance to your real bank.

Import behaviour:

  • Each balance transaction becomes one bank transaction for the gross amount (externalId = balance transaction id).
  • A non-zero fee becomes a second transaction with the opposite sign (externalId = <id>:fee, counterparty "Stripe") — so fees can be matched/posted as expenses separately from revenue. Fees returned on refunds come in as positive amounts.
  • Payout rows import as single negative transactions; match them against the corresponding incoming transaction on your real bank account statement.
  • Zero-amount rows are skipped; the date is taken from created_utc (UTC).
  • Payments-export rows that moved no money are skipped: Captured = false (authorizations/delayed captures) and Status Failed/Canceled.
  • A non-zero Amount Refunded becomes a third, negative transaction (externalId = <id>:refund) dated Refunded date (UTC) — so partial and full refunds embedded in payment rows are not lost.

PSP settlement reconciliation (Stripe payout reports)

For splitting a lump-sum payout into its underlying orders, refunds, fees and chargebacks, use the settlement endpoints instead of the plain statement import. Two Stripe exports are accepted (detected automatically):

  • Reports → Payouts → Payout reconciliation, itemized (report types payout_reconciliation.itemized.* or payout_reconciliation.by_id.itemized.*) — exact, one row per balance transaction; preferred on live accounts.
  • Payments → Export (unified payments) — the charge-level export; works in test/sandbox mode where payout reconciliation is unavailable. Captured charges are grouped by the payout id in the Transfer column; uncaptured/failed rows are skipped (skippedNotSettled); a non-zero Amount Refunded becomes a synthetic refund line in the charge's payout batch — an approximation, since the refund may really have settled in a later payout. Match references come from the charge id, PaymentIntent ID, Invoice Number, Client Reference ID and every … (metadata) column (e.g. an order number in metadata).

One CSV can span many payouts. Stripe downloads the itemized report one file per section (…_charge_…, …_refund_…, …_fee_…); import them all — rows for an already-imported payout merge into the existing batch (reported in the updated counter) as long as it has not been posted yet, and batch totals are recomputed.

POST /v1/bank/settlements/import
{
  "bankAccountId": "…",
  "provider": "stripe",
  "content": "balance_transaction_id,created_utc,…,reporting_category,…,automatic_payout_id,…"
}

Import behaviour:

  • Rows are grouped by automatic_payout_id into settlement batches — one batch per payout, with gross/fee/net totals (sum of row net = the amount that hits your bank). Re-importing a payout is skipped (skipped counter). Rows not yet assigned to a payout and the payout rows themselves are skipped and counted separately.
  • Every charge/refund/dispute row is auto-matched to a sale invoice using, in order: an e-commerce order's externalRef (any value from order_id, payment_intent_id, source_id, charge_id or any *_metadata[...] column), an invoice fullNumber appearing in those columns, and — for refunds and chargebacks — the charge_id of a previously matched charge (same file or earlier imports).
  • Review with bank/settlements/list / bank/settlements/get; fix leftovers with bank/settlements/match { lineId, invoiceId } (or invoiceId: null to unmatch).

POST /v1/bank/settlements/post { id, date?, commissionPercent? } posts the whole batch as one balanced journal transaction and settles the matched invoices (paidAmount/paymentStatus, sale_invoice.paid webhooks):

  • Debit the bank account GL for the net payout; debit PSP fees (settlements.fees, default 6800) for withheld fees and fee rows.
  • Credit accounts receivable per matched charge; refunds and chargebacks reverse it. Application is capped at each invoice's remaining amount — any excess lands on the suspense account with a warning.
  • Unmatched charges: with commissionPercent set they are split into commission revenue (settlements.commissionRevenue, default 5001) and a seller liability (settlements.sellerPayable, default 4499) — the marketplace flow where collected money mostly belongs to sellers. Without it they are parked on settlements.suspense (default 4440, advances received).
  • platform_earning rows (Stripe Connect application fees) post to commission revenue; transfer rows (payouts to connected sellers) reduce the seller liability.
  • All four accounts are overridable per company via posting rules (ledger/posting-rules).

VAT on the commission itself is invoiced separately (issue a commission invoice to the seller); the split only allocates the collected cash. Settlements are posted in the company base currency — foreign-currency payouts must currently be posted manually.

Manual / custom feeds

Anything else can be pushed as plain JSON:

POST /v1/bank/transactions/import
{
  "bankAccountId": "…",
  "transactions": [
    { "date": "2026-07-01", "amount": "-25.00", "currency": "EUR",
      "counterpartyName": "…", "description": "…", "externalId": "unique-ref-1" }
  ]
}

Provide a stable externalId per transaction to keep re-imports idempotent.