ING Kontoauszug Parser
A Ruby gem for parsing ING Bank (Germany) statement PDFs and text exports into structured JSON.
Why This Gem?
ING Germany provides account statements as PDFs, but extracting transaction data programmatically is challenging:
- PDF text extraction is messy – Column alignment, page breaks, and OCR artifacts make raw text unreliable
- No official export API – ING doesn't provide a machine-readable format like CSV or OFX
- Manual categorization is tedious – Hundreds of transactions need consistent tagging for budgeting
This gem solves these problems by:
- Extracting clean text from statement PDFs using poppler (fast) or pdf-reader (portable)
- Parsing transactions into structured data with dates, amounts, recipients, and narratives
- Extracting SEPA metadata like mandate IDs and references for reconciliation
- Validating IBANs using the ISO 13616 checksum algorithm
- Detecting payment methods like Google Pay for automatic categorization
Use it to build budgeting tools, import transactions into accounting software, or automate expense tracking.
Installation
# Gemfile
gem 'ing_kontoauszug_parser'bundle installQuick Start
require 'ing_kontoauszug_parser'
parser = IngKontoauszugParser::StatementParser.new
result = parser.parse(file_path: 'statement.pdf')
result[:header][:iban] # => "DE89 3704 0044 0532 0130 00"
result[:statements].first[:amount_eur_numeric] # => "-31.49"API
parser = IngKontoauszugParser::StatementParser.new
# Parse PDF file
result = parser.parse(file_path: 'statement.pdf')
# Parse text export
result = parser.parse_text(File.read('export.txt'))
# Parse pre-split lines
result = parser.parse_lines(lines)
# Parse booking lines only (no header)
statements = parser.parse_statement_lines(lines)Output Format
{
"header": {
"iban": "DE89 3704 0044 0532 0130 00"
},
"statements": [
{
"booking_date": "01.08.2025",
"transfer_type": "Lastschrift",
"recipient": "Allianz Direct Vers.",
"amount_eur": "-31,49",
"amount_eur_numeric": "-31.49",
"amount_direction": "debit",
"value_date": "01.08.2025",
"narrative": "Versicherungsbeitrag August",
"mandate_id": "MA123456",
"reference": "RF123456789",
"google_pay": true
}
]
}Statement Fields
| Field | Type | Description |
|---|---|---|
booking_date |
string | Booking date (dd.mm.yyyy) |
transfer_type |
string | Transaction type (e.g., Lastschrift, Gutschrift) |
recipient |
string | Counterparty name |
amount_eur |
string | Original amount with German formatting |
amount_eur_numeric |
string | Normalized decimal (BigDecimal as string) |
amount_direction |
string |
debit, credit, or neutral
|
value_date |
string | Value date (dd.mm.yyyy) |
narrative |
string | Transaction details |
mandate_id |
string? | SEPA mandate ID (if present) |
reference |
string? | SEPA reference or ARN (if present) |
google_pay |
boolean? |
true if Google Pay detected |
CLI Tools
# Parse PDF statement to JSON
bin/pdf_to_json statement.pdf
# Write to file instead of stdout
bin/pdf_to_json -o output.json statement.pdf
# Interactive console
bin/consoleError Handling
PDF reader errors are wrapped as IngKontoauszugParser::Error:
begin
parser.parse(file_path: 'encrypted.pdf')
rescue IngKontoauszugParser::Error => e
puts e.message
endNon-fatal issues (e.g., statements missing value dates) are collected in an optional warnings array:
result = parser.parse_text(text)
result[:warnings]&.each { |w| puts "Warning: #{w}" }Development
bin/setup # Install dependencies
rake test # Run tests
rake rubocop # LintRequirements
- Ruby 3.0+
- pdf-reader gem
- Input files must be UTF-8 encoded
Documentation
Contributing
Bug reports and pull requests welcome on GitHub.