File and Batch Interfaces

The least fashionable and most durable integration style. Banks, payroll providers, government agencies, and 3PLs all still exchange files, and they will still be doing so in ten years.

Why files persist

  • Banks and government systems mandate them
  • Robust to outages — the file is still there tomorrow
  • Easy to inspect, archive, and replay
  • No availability coupling between the two systems
  • Business users can produce and consume them (Excel)
  • High volume is cheap

Formats

Format Notes
CSV Ubiquitous, deceptively problematic — quoting, embedded newlines, encoding, delimiter conflicts
Fixed width Banks and legacy mainframes. Byte-position based, unforgiving, but unambiguous
Excel (xlsx) What users actually send. Dates, leading zeros, and number formatting will hurt you
XML Structured, validatable with XSD, verbose. ISO 20022, e-invoicing
JSON Modern, no standard schema validation unless you add it
X12 / EDIFACT See EDI
Parquet Analytics bulk loads, columnar, compressed

The CSV traps

Every one of these has caused a production incident somewhere:

1. Delimiter in the data:       "Rahman Textiles, Ltd"  → must be quoted
2. Quotes in the data:          "The ""Blue"" Line"      → escaped
3. Newlines in a field:         multi-line addresses     → must be quoted
4. Encoding:                    UTF-8 vs Latin-1 vs UTF-16 vs UTF-8-with-BOM
5. Line endings:                CRLF vs LF
6. Leading zeros stripped:      material 0001234 → 1234  (Excel's fault, always)
7. Long numbers → scientific:   8901234567890 → 8.90123E+12
8. Dates reformatted:           15/03/2026 → 03/15/2026 → 2026-03-15, silently
9. Locale decimal separator:    1.234,56 vs 1,234.56
10. Trailing whitespace:        "DHK1 " ≠ "DHK1"
11. No header, or a changed header order
12. A blank line at the end producing a phantom empty record

Rules that prevent most of them:

  • Always UTF-8, and document it. Handle the BOM.
  • Always quote every field, not just those needing it.
  • ISO 8601 dates (2026-03-15) only.
  • Decimal point, never comma, with no thousands separator.
  • Treat all identifiers as strings, never numbers.
  • Validate the header row matches the expected contract before processing anything.
  • Reject the file on a checksum or record-count mismatch, don’t process it partially.

File naming and control

INVOICES_1000_20260315_001.csv
   │      │      │       └── sequence within the day
   │      │      └────────── date
   │      └───────────────── company code
   └──────────────────────── content type

Control/trailer records prove completeness:

HDR|INVOICES|1000|20260315|001
DTL|INV-001|C-100455|11500.00|BDT
DTL|INV-002|C-100456|22300.00|BDT
...
TRL|1247|14882140.00
    │        └── hash total of the amount column
    └──────────── record count

Always validate the trailer before processing. A truncated file that processes 800 of 1,247 invoices and reports success is a genuinely bad day. The record count and hash total catch it instantly.

Transport

Method Notes
SFTP The standard. Key-based auth, not passwords
FTPS Older, firewall-hostile
Plain FTP Never. Credentials and data in clear text
AS2 EDI, with signed receipts
HTTPS upload/download Simple, works with modern auth
Cloud storage (S3, Blob, GCS) Increasingly the default; use event notifications
Email attachment Common, fragile, insecure. Avoid for anything structured
EBICS European banking standard

The partial-file race

Sender is still writing INVOICES.csv
Receiver's poller sees the file and starts reading
→ processes half a file

Solutions:

  1. Write to a temp name, then rename — rename is atomic on most filesystems. The best answer.
  2. Marker/trigger file — write INVOICES.csv, then INVOICES.done; the poller waits for the marker
  3. File size stability check — poll until the size stops changing. Crude, works
  4. Event-driven — cloud storage notifications fire on completed upload

This is the most common file-interface bug. Design against it explicitly.

Processing pipeline

1. POLL / receive          detect a complete file
2. MOVE to processing      atomic move out of the inbound folder — prevents double pickup
3. ARCHIVE the raw file    before any parsing. You will need the original
4. PARSE                   syntax errors → reject the whole file
5. VALIDATE control totals record count, hash totals
6. VALIDATE records        business rules, per record
7. LOAD                    per record or in chunks
8. REPORT                  processed / failed with reasons
9. ACKNOWLEDGE             a response file, an API call, or an email
10. MOVE to done / error

Folder structure

/inbound/           files land here
/inbound/processing/ moved here on pickup (atomic move prevents double processing)
/archive/2026/03/15/ raw files, retained
/error/             failed files, with an error report alongside
/outbound/          files we produce
/outbound/sent/     after successful transmission

Error handling: all-or-nothing vs per-record

ALL-OR-NOTHING            PER-RECORD
Any error → reject file   Good records load, bad ones go to an error file
✅ Consistent             ✅ Progress; one bad row doesn't block 10,000 good ones
❌ 1 bad row blocks all   ❌ Partial state; needs reconciliation

Choose per interface, and document it.

  • Bank statements: all-or-nothing (a partial statement corrupts the reconciliation)
  • Master data loads: per record (load the 9,997 good customers)
  • Payment files: all-or-nothing (partial payment runs are unacceptable)
  • Sales order imports: per record, with a clear error file back to the sender

Reprocessing

Files get reprocessed: an error was fixed, a load was interrupted, a partner resent.

Requirements:

  • Idempotency — reprocessing must not duplicate. Key on (file name + record key) or the business key
  • Duplicate file detection — hash the file; if you’ve seen this hash, warn loudly
  • Partial reprocess — reload only the previously failed records
  • Audit — who reprocessed, when, why
file_load(
  id, interface_name, file_name, file_hash, received_at,
  record_count, records_loaded, records_failed,
  status, processed_at, processed_by,
  UNIQUE(interface_name, file_hash)   -- duplicate file detection
);
file_load_record(
  load_id, line_number, business_key, status, error_message, entity_id
);

Excel imports

Business users will send spreadsheets. Design for it rather than fighting it.

Good practice:

  1. Provide a template with the exact columns, data types, and validation
  2. Validate the header before reading data
  3. Read cells as text, then parse deliberately — never trust Excel’s typing
  4. Return an annotated error file: the user’s own spreadsheet with an added “Error” column and the failing cells highlighted. They fix it and re-upload. This one feature makes an import tool genuinely usable.
  5. Preview before commit — show the first 20 parsed rows and the totals
  6. Size limits and a row cap

Bank file formats

Worth a specific mention because they’re strict, non-negotiable, and everywhere:

Format Use
ISO 20022 pain.001 Credit transfer initiation (XML)
pain.008 Direct debit initiation
camt.053 / .052 / .054 Statement / intraday / notification
MT940 / MT942 SWIFT statement (semi-structured text)
BAI2 US bank statements
NACHA US ACH (fixed width)
Local formats BEFTN (Bangladesh), NEFT/RTGS (India), and many others

Banks reject files for a single character out of place. Always validate against the bank’s XSD before sending, and test with the bank’s validation tool if they provide one.

Monitoring

Check Alert
Expected file did not arrive by the SLA time Critical — silent failure is the worst kind
File arrived but failed validation Error
Record count deviates >20% from the usual Warning — something changed upstream
Error records above threshold Warning
Outbound transmission failed Error
Archive folder growth Capacity
SFTP key or certificate expiry 30 days ahead

“The file didn’t arrive” is the most important alert. A missing file produces no error anywhere; the system simply does nothing, and nobody notices until month-end. Every scheduled inbound interface needs an expected-arrival monitor.

Developer checklist

  • UTF-8, documented, BOM handled
  • All identifiers treated as strings
  • ISO 8601 dates
  • Control record with count and hash total, validated before processing
  • Atomic pickup (temp name + rename, or a marker file)
  • Raw file archived before parsing
  • Duplicate file detection by hash
  • Idempotent record loading
  • Per-record vs all-or-nothing decided and documented
  • Error file returned to the sender in a usable format
  • Reprocess capability with an audit trail
  • Missing-file alerting
  • Tested with a truncated file, an empty file, a file with only a header, and a file with 500,000 rows

Next