BetaYou're exploring an early version of tPay365. Features and content may change as we refine the experience.

← Back to Features
Compatibility Engine

BACS Standard 18 Parser

Legacy compatible. Future ready. We speak the language of the 1970s so your HR team doesn't have to.

Zero migration. Zero retraining.

90% of UK employers run payroll through BACS Standard 18 — a fixed-width file format from the 1970s. tPay365 reads it natively. HR uploads the same file they've always uploaded. No new software. No retraining. No migration project.

For the other 10%, we accept CSV uploads as a fallback. But for the vast majority, integration is as simple as pointing the existing payroll export at tPay365.

1

Upload

HR uploads the same BACS Standard 18 file they already generate. Drag and drop or API.

2

Parse & Validate

Fixed-width records are sliced, validated, and enriched. Errors collected per-line, never thrown.

3

Orchestrate

Parsed records trigger the Clean Paycheck engine. Each employee gets vaults, routing, and yield.

From fixed-width to structured data

A real BACS Standard 18 file, parsed into clean JSON in under 50ms

Raw BACS Standard 18
VOL1
HDR1A000000            0000000000001  000000 0260210 0260210 000000
HDR2F0200000100
UHL1 260210    000000    000001
0617943012345678TPAY365LTD      00000250000ALICE SMITH         1720241501
0617943087654321TPAY365LTD      00000180000BOB JONES           1720241501
EOF1A000000            0000000000001  000000 0260210
EOF2F0200000100
UTL10000000000000000043000000000000000002          0000000000000000000000000                       
Parsed JSON output
{
  "header": {
    "processing_date": "2026-02-10",
    "file_number": "000001",
    "originator": "TPAY365LTD"
  },
  "records": [
    {
      "sort_code": "617943",
      "account_number": "01234567",
      "amount_pence": 250000,
      "amount_pounds": 2500.00,
      "name": "ALICE SMITH",
      "reference": "172024",
      "transaction_code": "99",
      "status": "valid"
    },
    {
      "sort_code": "617943",
      "account_number": "87654321",
      "amount_pence": 180000,
      "amount_pounds": 1800.00,
      "name": "BOB JONES",
      "reference": "172024",
      "transaction_code": "99",
      "status": "valid"
    }
  ],
  "trailer": {
    "total_debit_pence": 430000,
    "record_count": 2,
    "checksum": "valid"
  },
  "parse_errors": []
}

Parsing rules

Records are exactly 100 chars (81 minimum)
string.slice() only — never regex
Never .trim() the full line — BACS has significant spaces
Leading zeros preserved — "00000250000" = £2,500.00
Amounts in pence — integer division for pounds
Errors collected per-line, never thrown

Technical Architecture

Technical Specification

Format: BACS Standard 18 (fixed-width, 100 chars/record)
Record types: VOL, HDR1, HDR2, UHL1, entries, EOF, UTL
Parsing: string.slice() — never regex on fixed-width
Validation: Modulus check on sort code + account number
Idempotency: hash(content + employer_id + pay_period)
Error handling: Collects per-line errors, returns partial
Fallback: CSV upload for non-BACS systems
Max file: 10MB, 10,000 records

Parse speed

<50ms

For 10,000 record file

Max file size

10 MB

10,000 records per file

Error handling

Partial results

Errors collected, never thrown

Disaster recovery

Server B (this application) is the primary BACS parser. If it fails, Server A activates its Python fallback implementation. Both produce identical outputs, verified via shared test fixtures. Zero downtime for payroll processing.

bacs-upload-dashboard.pngHR dashboard with drag-and-drop BACS file upload interface
bacs-validation-report.pngValidation report showing parsed records with status indicators

API Endpoints

POST/api/v1/payroll/uploadUpload BACS file
POST/api/v1/payroll/parseParse uploaded file
POST/api/v1/payroll/validateValidate parsed data
GET/api/v1/payroll/batch/{id}/statusCheck batch status
View full API reference →