The import reads an XLSX, CSV or PDF statement of booked payments. There are presets for some layouts, other layouts are read automatically, and a column mapper remains as a last resort. It has been tested with Holvi exports so far. A mapping says which column holds which value. A layout the app works out, or that you set in the column editor, is saved on a successful import and used again for files with the same column names.
Flow
| Step | Page | Request | Writes |
|---|---|---|---|
| Import | Import a bank statement (/import), one file field and Import statement |
POST /import |
R2 original, D1 import and bank rows |
| Which account (only when unclear) | The same page, asking Which account is this? | POST /import with account |
As above |
| Column editor (only when needed) | /imports/csv |
POST /imports/csv/preview (button Check again), then POST /imports/csv/confirm (button Import N payments) |
Nothing until the confirm |
| Import anyway | /import/anyway |
POST /import/anyway |
Extra payments, as a second import of the same file |
The account is, first hit: the one chosen on the question, the only account, the account whose bank the layout names, a new account named for that bank when there is none, else the question. The layout is, first hit: a layout saved for these column names, the built-in layout for them, the model’s proposal when it reads the whole file, else the column editor. The editor also opens when rows have errors, the stated totals do not add up, or the delimiter or a slash date order is ambiguous. A file that is not UTF-8 is read as Windows-1252.
In the editor, the preview reads the whole file and shows up to 50 rows and up to 100 row problems. Any row problem blocks the import. A preview is valid for 30 minutes and is signed. After import the browser goes to Bank for the month of the first row, where the banner offers Undo for the whole import.
Column editor fields
The editor shows these with the file it was given.
| Label | Field | Values |
|---|---|---|
| Bank statement (XLSX, CSV or PDF) | file |
.xlsx, .csv, .pdf or .txt file |
| Which account is this? | account |
An existing account |
| Import into | account_action |
existing: the account chosen here; new: a new account |
| Account name | account_label |
New account only |
| Bank | bank_name |
New account only |
| Currency | account_currency |
New account only. One of the ten supported currencies. |
| Text encoding | encoding |
utf-8, utf-16le, utf-16be or windows-1252, shown as those values. The form also accepts auto, which the one-step import uses: UTF-8 or the file’s BOM, else Windows-1252. |
A new account is created only when the import is confirmed. The editor’s Text encoding sits under The file.
Mapping fields
Shown under The columns look wrong? Adjust how we read the file. Column numbers in the app start at 1; row numbers for Which row has the column names? start at 0.
The file
| Label | Mapping key | Values |
|---|---|---|
| Layout | Choose layout, Custom mapping, or a saved or built-in layout that matches the column names | |
| Text encoding | encoding |
utf-8, utf-16le, utf-16be, windows-1252 |
| Column separator | delimiter |
Comma, Semicolon, Tab, Pipe |
| Which row has the column names? | headerRecord |
0 to 100. Rows above it are left out. |
| Rows to skip at the end | trailingRecords |
0 to 100 |
Dates
| Label | Mapping key | Values |
|---|---|---|
| Date column | date.column |
Required |
| Date format | date.format |
ISO (YYYY-MM-DD), DD.MM.YYYY, DD/MM/YYYY, MM/DD/YYYY, ISO_OR_DOTTED (either YYYY-MM-DD or DD.MM.YYYY per row), D MMM YYYY (31 May 2026, 1 Sept 2026: English month names, short or full) |
| Dates include a time | date.time |
No (none), Sometimes (optional) |
With Sometimes, a date may be followed by T or a space and HH:MM, optional seconds and fractions, and an optional Z or +HH:MM offset. The printed calendar day is kept. A date format with slashes needs the tick The day and month order above is right for dates written with slashes.
Amounts
| Label | Mapping key | Values |
|---|---|---|
| Decimal mark | number.decimal |
., ,, dot-or-comma |
| Thousands separator | number.grouping |
none, ., ,, space, ' |
| How amounts are shown | amount.kind |
One amount column (signed), Separate money out and money in columns (split), An amount column plus an in or out column (direction) |
| Amount column | amount.column or amount.amount |
For signed and direction |
| Money out is shown as | amount.outgoing |
Negative numbers, Positive numbers. For signed. |
| Money out column | amount.debit |
For split |
| Money in column | amount.credit |
For split |
| In or out column | amount.direction |
For direction |
| Words that mean money out | amount.debit |
For direction. Exact text; several separated by |. Default OUT. |
| Words that mean money in | amount.credit |
For direction. Default IN. |
dot-or-comma takes whichever of . or , appears and cannot be combined with a thousands separator. space accepts a space, a no-break space or a narrow no-break space. Groups must be 3 digits after a first group of 1 to 3. Money out is stored as a negative amount.
Currency and names
| Label | Mapping key | Values |
|---|---|---|
| Currency | currency.kind |
The same for every row (default), Read from a column (column) |
| Currency for every row | currency.currency |
For default |
| Currency column | currency.column |
For column |
| Currency when that column is empty | currency.blankDefault |
For column. Empty means a blank cell is an error. |
| Paid to column | merchant |
Required. A blank cell is an error. |
| Reference column | reference |
Optional |
moreReferences |
Optional, set by layouts only. Up to 5 more text columns, such as a message or description, joined onto the reference with /. A value already contained in an earlier one is left out. |
|
| Transaction ID column | externalId |
Optional. The bank’s own ID for the row. |
| Original amount column | original.amount |
Optional. Must be paired with the next. |
| Original currency column | original.currency |
Optional. Must be paired with the previous. |
Already imported?
| Label | Field | Values |
|---|---|---|
| If a payment was already imported | collision |
Ask me (empty), Skip the ones already imported (keep_existing), Import them as extra payments (additional) |
| I checked the account. Similar payments in my other accounts are separate payments. | acknowledge_other_account |
Tick |
Saved layouts
There is no form for this. A layout the model proposed or you set in the editor is saved when the import succeeds, with a fingerprint of the normalised column names, and is used for files whose column names give the same fingerprint, the account’s bank first. Built-in and saved layouts are not saved again. A failure to save the layout does not undo the import.
Accepted formats
| Item | Accepted |
|---|---|
| Encodings | UTF-8, UTF-16 LE, UTF-16 BE, Windows-1252. A byte order mark selects the UTF encoding. Without one, the file must be valid in the chosen encoding. |
| Files | .xlsx (first sheet; inline and shared strings, numbers, and dates stored as Excel serial numbers) and CSV. A spreadsheet is turned into a CSV on upload, and that CSV is what is checked and stored as the original. |
| A statement with a text layer, up to 400 pages. It is turned into a CSV on upload the same way; see PDF statements. | |
| Separators | , ; tab |. Detected from the column names row when not chosen. |
| Currencies | EUR, USD, GBP, CHF, SEK, NOK, DKK, CAD, AUD, JPY |
| Amounts | Integer minor units. More decimals than the currency has are allowed only when they are zero. |
| Not accepted | Scanned PDFs without text, .xls, bank API connections, filtering of pending rows |
Built-in layouts
Built-in layouts are matched by column names. They were tested with synthetic files only and are named after the CSV layout, not the bank. Presets match common CSV export layouts; Nuits Receipts is not affiliated with any bank.
| Layout | Matches column names | Date | Decimal | Separator |
|---|---|---|---|---|
| Holvi (CSV layout), statement | Value date, Booking date, Amount, Currency, Counterparty, Description, Filing ID (XLSX or CSV statement). Date from Booking date; Reference, Description and Message joined into the reference; Filing ID is the transaction ID; a Currency and amount: USD 10.46 line in Description is the original amount and currency. |
D MMM YYYY |
. |
, |
| PDF statement (CSV layout) | Date, Amount, Counterparty, Description, Reference, Transaction ID, Currency, in that order: what a PDF statement of another bank becomes on upload. A blank currency is the account currency. |
ISO |
. |
, |
| Holvi (CSV layout) | Recognised aliases for date, payee, amount or debit and credit, and optional currency, reference, transaction ID and original amount and currency | ISO_OR_DOTTED |
dot-or-comma |
; |
| Nordea (CSV layout) | Kirjauspäivä, Saaja, Summa, Valuutta, Viite |
DD.MM.YYYY |
, with space grouping |
; |
| OP (CSV layout) | Same as Nordea | DD.MM.YYYY |
, with space grouping |
; |
| Revolut (CSV layout) | Started Date, Completed Date, Description, Amount, Currency, ID |
ISO, date from Completed Date |
. |
, |
| Wise (CSV layout) | ID, Date, Amount, Currency, Description, Reference |
ISO |
. |
, |
| N26 (CSV layout) | Date, Payee, Payment reference, Amount (EUR), Currency |
ISO |
. |
, |
When exactly one layout matches, it is applied automatically. When several match, you choose one or Custom mapping.
Column names row
For a new file, the row with the column names is found automatically: the first row of two or more names with no gaps, none of them a number or a date, whose following rows fit under it. Rows above it, such as an account line or stated totals, are left out and listed under the preview. When no row qualifies, the first row is used. Which row has the column names? overrides it.
Stated totals
Above the column names, a label that names a volume, sum or amount, or a count or number, of credits or debits (for example Total volume of credits and Total count of debits) is read with the figure in the cell below it. Credits are compared with the payments read as money in and debits with money out. Opening balance and Closing balance are read the same way: the opening balance plus every payment must come to the closing balance. A different count, sum or balance is a problem and blocks the import.
Layouts read automatically
When no saved or built-in layout matches, one request goes to Workers AI (Clef). It sends only the column names and up to 5 rows, each cell cut to 40 characters, and asks which column holds the date, the amount (or money out and money in), the currency, the payee, a description, a reference and the transaction ID, plus the date format and how money out is shown. The answer can only name columns of the file. The decimal mark and the date format are confirmed against the sample rows, and the account currency is used when there is no currency column.
The proposal is used only if the whole file then reads with no row problems. Otherwise, or if the request fails, the page asks for the columns as before. The proposal travels with the preview, so Check again and Import make no further requests. With AI_MODE mock, columns are picked by their names instead.
PDF statements
A PDF statement is turned into a CSV on upload, with the statement’s printed balances and totals above the column names, so the checks above apply to it. The text is read with its position on the page, which keeps the columns apart.
- Holvi (
EUR - ACCOUNT STATEMENT, columnsBooking date,Payment information,Transaction no.,Amount EUR) is read by its layout, with no model request. Each payment becomes a row of the Holvi statement layout: the booking date, the signed amount, the other party from the payment information (an IBAN line left out), the payment type, theMessageandReferenceparts, theCurrency and amountline of a card payment abroad, and theFiling IDas the transaction ID. TheBALANCElines and theTOTAL NUMBER OF DEPOSITSandWITHDRAWALSlines are the stated figures. A line in no known place stops the read instead of being skipped. - Other layouts are cut into one block per payment: a line that starts with a date, up to the next one. Lines printed in the same place on most pages, such as page headers and footers, are left out, and labelled opening and closing balance and total lines are read as stated figures. Workers AI (Clairic) reads the date, amount, currency, other party, description, reference and transaction ID of each block, 20 blocks per request and up to 4 requests at a time, at most 1,000 blocks. Each value is then checked against the block’s own text: the amount must be printed in it, the date must be a reading of its leading date, and text values must appear in it. A value that fails, or that Clairic is not sure of, is left blank, so the row is listed as a problem.
The PDF is read once, on upload. Check again and Import use the CSV it became, which is also what is stored as the original. With AI_MODE mock, other layouts are read by a fixed local reader instead.
Column name aliases (Holvi layout)
Column names are compared after normalisation (lower case, accents removed, punctuation as spaces).
| Value | Aliases |
|---|---|
| Transaction ID | transaction id, transaction number, tapahtumatunnus, tapahtuma id |
| Date | booking date, date, kirjauspaiva, tapahtumapaiva |
| Amount | amount, summa, maara |
| Currency | currency, valuutta |
| Paid to | payee, recipient, counterparty, name, saaja, maksaja, nimi |
| Reference | reference, message, description, viite, viitenumero, viesti, selite |
| Original amount | original amount |
| Original currency | original currency |
| Money out | debit |
| Money in | credit |
Row problem codes
Shown in the preview as “Row N, field: message (code)”.
| Code | Meaning |
|---|---|
csv_syntax |
The CSV parser could not read the row. |
column_count |
The row has a different number of columns from the column names row. |
mapping_column |
A mapped column is not in the column names row. |
invalid_date |
The date does not match the format, or a time is present when not allowed. |
invalid_merchant |
The paid-to cell is empty. |
invalid_currency |
Unsupported or empty currency. |
invalid_amount |
The amount does not parse, the debit and credit cells conflict, or the sign conflicts with the direction word. |
invalid_original_currency |
Unsupported original currency. |
invalid_original_amount |
Original amount does not parse, or its sign differs from the amount. |
Duplicates and re-imports
| Situation | Result |
|---|---|
| Same file bytes, same account, import complete | Nothing new is imported. Existing matches and exemptions stay. |
| Same file bytes, same account, import pending | The import resumes with the settings it was started with. |
| Same file bytes, another account | Refused with HTTP 409. |
| Row with a transaction ID already stored in this account with the same facts | Skipped. |
| Row with a transaction ID already stored in this account with different facts | Import refused with HTTP 409. |
| Row without a transaction ID equal to one from another file in the same account | Skipped and counted; the banner offers Import anyway, which adds them as extra payments. In the editor, If a payment was already imported chooses (keep_existing or additional). |
| Equal rows without a transaction ID inside one file | Kept as separate payments. |
| Same date and amount, or same transaction ID, in another account | Imported and counted in the banner. In the editor it needs the account tick. Rows are never merged across accounts. |
A row’s identity is a hash of the account and either the transaction ID or its facts: booking date, amount, currency, normalised merchant, reference, original amount and original currency, plus a count of equal rows before it in the file.
Limits
| Limit | Value |
|---|---|
| File size | 2 MiB |
| Rows | 1 to 5,000, not counting the column names row and skipped rows |
| Columns | 100 |
| Cell size | 64 KiB decoded |
| Stored raw row | 256 KiB of JSON |
| Saved mapping | 32 KiB |
| Import record (manifest) | 1 MiB |