Vertical SaaS · Integration

Acumatica SFTP Automation for Bank Statements

How to automate Acumatica bank statement import via SFTP — picking up files from the bank, parsing, importing, and reconciling without a human in the loop.

John Kihiu12 min read

Most bank-statement imports in Acumatica start as a person downloading a file from the bank portal every morning and uploading it on the Import Bank Transactions screen. That works until the person is on leave. The reliable version is a scheduled pull: the bank drops a statement file on an SFTP server, a job collects it, and Acumatica imports and matches it without anyone in the loop. The moving parts are the SFTP fetch, the file format, the import mapping, and idempotency so nothing gets loaded twice.

The pieces of the pipeline

Break the job into four stages, each independently retryable: fetch the file over SFTP, stage it somewhere durable, import it into Acumatica's bank-transaction staging area, and reconcile against the cash account. Keeping them separate matters because they fail for different reasons — the bank's SFTP being down is not the same problem as a malformed row, and you want to retry the fetch without re-importing what already landed.

Fetching over SFTP

SFTP is SSH file transfer, so authenticate with a key pair rather than a password and pin the bank's host key so you fail loudly if the endpoint changes. On the .NET side a library like SSH.NET handles the transport; the job lists the remote directory, downloads anything new, and — critically — moves or renames the file on the server (or records what it has seen) so the next run does not pick it up again.

C# · SFTP FETCH
using (var client = new SftpClient(host, port, user, new PrivateKeyFile(keyPath)))
{
    client.HostKeyReceived += (s, e) =>
        e.CanTrust = e.FingerPrintSHA256 == ExpectedFingerprint; // pin the host key
    client.Connect();

    foreach (var file in client.ListDirectory(remoteDir))
    {
        if (file.IsDirectory || !file.Name.EndsWith(".csv")) continue;
        if (AlreadyProcessed(file.Name)) continue;          // idempotency guard

        using (var local = File.Create(Path.Combine(stageDir, file.Name)))
            client.DownloadFile(file.FullName, local);

        client.RenameFile(file.FullName, $"{archiveDir}/{file.Name}"); // don't re-fetch
        RecordProcessed(file.Name);
    }
    client.Disconnect();
}
Idempotency is the whole game

Banks re-send files, and a job that crashes mid-run will restart. If you import the same statement twice you double every transaction and the reconciliation will never balance. Track processed files by name and a content hash, and let the database's unique constraint on the external transaction ID be your last line of defence.

Parsing the format

Bank statement files come in a handful of shapes: delimited CSV, fixed-width, MT940, BAI2, or CAMT.053 (ISO 20022 XML). Acumatica's bank import framework can consume several of these through a Bank Feed or an import scenario with a mapping, but the format the bank actually sends decides everything downstream. Nail down which one you are getting and whether amounts use debit/credit columns or a signed value — that single ambiguity causes more reconciliation mismatches than any parsing bug.

FormatShapeNotes
CSVDelimited textEasiest, but column order varies per bank
MT940SWIFT statementCommon for corporate accounts
BAI2US bank formatType codes identify each transaction
CAMT.053ISO 20022 XMLRichest data, the modern standard

Importing into Acumatica

The clean path is Acumatica's own bank-statement import: push the staged rows into the bank transactions staging table, then let the standard Process Bank Transactions flow match them. Prefer this over writing directly to GL — the framework already handles the matching rules, the cash account link, and the reconciliation record. If you must load programmatically, the contract-based API or an import scenario against the bank-import screen keeps you inside supported territory rather than poking tables directly.

Reconciliation and matching

Once transactions are staged, Acumatica matches them against open payments and deposits by amount, date, and reference. Configure the matching rules so the common cases auto-match and only exceptions land on a human's desk. The realistic target is not 100% automated matching — it is that the exceptions are few, obvious, and surfaced quickly, so month-end reconciliation is a review rather than a re-keying exercise.

Log what didn't match, loudly

The silent failure mode here is a file that imports cleanly but leaves half its lines unmatched, quietly, for three weeks. Emit a per-run summary — files fetched, rows imported, matched, unmatched — and alert when the unmatched count crosses a threshold. A reconciliation you can't see is a reconciliation you don't have.

Scheduling and monitoring

Run the fetch on a schedule that follows the bank's posting time, not an arbitrary cron slot — pulling at 6 AM for a file the bank posts at 8 AM just guarantees an empty run and a late import. Give the job a heartbeat and alert on absence: a statement that never arrives is as much an incident as one that fails to parse, and it is the failure people notice last.

Wrapping up

Automating bank-statement import is mostly plumbing done carefully: pull the file over SFTP with a pinned host key, guard hard against re-importing, hand the rows to Acumatica's own bank-transaction flow, and make both matches and non-matches visible. The result is a reconciliation that is a morning glance instead of a daily chore — and one that keeps working when the person who used to do it by hand is away. If you are wiring one up against a specific bank format, reach out or keep reading through the rest of the Acumatica blog.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.