Czech Bank Statement Parser API
REST API for extracting structured transaction data from Czech bank statement PDFs. Upload a PDF — get back JSON with account info, statement metadata, a full list of transactions with semantic tags, and a cryptographic verification of the PDF's embedded digital signature (bank issuer identity, integrity check, tampering detection — see the Signature object).
Supported banks: KB (/0100), MONETA (/0600), Air Bank (/3030), ČSOB (/0300), mBank (/6210), Raiffeisenbank (/5500), Fio (/2010), UniCredit (/2700), Česká spořitelna (/0800), Partners Banka (/6363).
Base URL
All endpoints are prefixed with /api/v1.
The full URL for each endpoint is shown in the examples below.
Authentication
Every request to a business endpoint must include a Bearer token in the
Authorization header:
Authorization: Bearer YOUR_API_KEY
API keys are issued manually by the operator — contact
ginibooster@gmail.com
to request one. The same header accepts a JWT access token obtained via
/api/v1/auth/login
for browser sessions.
New accounts created via /auth/register
must be approved by an administrator before they can call the business endpoints;
until approval the API returns
403. API-key callers
bypass this check — issuing a key implies approval.
Error codes
| Status | Meaning | Body |
|---|---|---|
| 200 | Parsing successful | {"result": {...}, "file_id": "..."} |
| 400 | Bad request — missing or invalid file | {"error": "..."} |
| 401 | Missing or invalid Bearer token | {"error": "..."} |
| 403 | Account awaits administrator approval | {"error": "..."} |
| 422 | File is not a recognised bank statement | {"error": "...", "file_id": "..."} |
| 500 | Unexpected server error | {"error": "...", "file_id": "..."} |
Upload a Czech bank statement PDF. Returns structured JSON with account details, statement period, all parsed transactions, and a verification of the PDF's embedded digital signature (see the Signature object).
Request
Two content types are accepted:
Option A —
Content-Type: application/json
| Field | Type | Description |
|---|---|---|
| filerequired | string | PDF file encoded as Base64 |
| filenameoptional | string | Original filename (must end with .pdf). Default: upload.pdf |
Option B —
Content-Type: multipart/form-data
| Field | Type | Description |
|---|---|---|
| filerequired | file | PDF file, max 20 MB |
Example — cURL (Base64 JSON)
B64=$(base64 -w 0 statement.pdf) curl -X POST https://api.ginibooster.cz/api/v1/bs_parse \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d "{\"file\":\"$B64\",\"filename\":\"statement.pdf\"}"
Example — cURL (multipart)
curl -X POST https://api.ginibooster.cz/api/v1/bs_parse \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@statement.pdf"
Example — Python (Base64 JSON)
import base64, requests with open("statement.pdf", "rb") as f: b64 = base64.b64encode(f.read()).decode() resp = requests.post( "https://api.ginibooster.cz/api/v1/bs_parse", headers={"Authorization": "Bearer YOUR_API_KEY"}, json={"file": b64, "filename": "statement.pdf"}, ) print(resp.json())
Response — 200 OK
{
"file_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890.pdf",
"result": {
"bank": "KB",
"account": {
"number": "123456789/0100",
"iban": "CZ65 0800 0000 0012 3456 7890",
"bic": "KOMBCZPP",
"owner": "Jan Novák",
"currency": "CZK",
"type": "Osobní účet"
},
"statement": {
"number": "12",
"period_from": "2026-01-01",
"period_to": "2026-01-31",
"date_issued": "2026-02-01",
"opening_balance": 12500.00,
"closing_balance": 18340.50,
"total_credited": 35000.00,
"total_debited": -29159.50
},
"transactions": [ /* see Transaction object below */ ],
"signature": { /* see Signature object below */ }
}
}
Submit a parsing error report linked to a previously uploaded file. Reports are stored server-side and reviewed manually.
Request
Content-Type: application/json
| Field | Type | Description |
|---|---|---|
| textrequired | string | Description of the parsing issue |
| file_idoptional | string | UUID from a previous /bs_parse response |
| original_nameoptional | string | Original filename of the uploaded PDF |
| bankoptional | string | Detected bank name from the parse result |
Example — cURL
curl -X POST https://api.ginibooster.cz/api/v1/report_bug \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "file_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890.pdf", "original_name": "statement.pdf", "bank": "KB", "text": "Transaction on 2026-01-15 is missing from the output." }'
Response — 200 OK
{ "ok": true }
Download or display a previously uploaded PDF by its UUID. Useful for embedding an in-browser PDF preview alongside the parse result.
Path parameter
| Parameter | Type | Description |
|---|---|---|
| file_idrequired | string | UUID returned by /api/v1/bs_parse, e.g. a1b2c3d4…pdf |
Example — cURL
curl https://api.ginibooster.cz/uploads/a1b2c3d4-e5f6-7890-abcd-ef1234567890.pdf \ -H "Authorization: Bearer YOUR_API_KEY" \ --output statement.pdf
Response
Returns the raw PDF file with Content-Type: application/pdf.
Response object
Top-level shape returned by /api/v1/bs_parse.
Top-level fields
| Field | Type | Notes |
|---|---|---|
| bank | string | One of KB, MONETA, AIRBANK, CSOB, MBANK, RAIFFEISENBANK, FIO, UNICREDIT, SPORITELNA, PARTNERS_BANKA |
| account | object | See account below |
| statement | object | See statement below |
| transactions | array | See the Transaction object |
| signature | object | See the Signature object — always present, reports integrity and signer identity |
account
| Field | Type | Notes |
|---|---|---|
| number | string|null | Local format, e.g. 123456789/0100 |
| iban | string|null | |
| bic | string|null | |
| owner | string|null | Account holder name |
| currency | string | Always "CZK" |
| type | string|null | Account type label |
statement
| Field | Type | Notes |
|---|---|---|
| number | string|null | Statement serial number |
| period_from | string|null | ISO date YYYY-MM-DD |
| period_to | string|null | ISO date YYYY-MM-DD |
| date_issued | string|null | ISO date |
| opening_balance | float|null | |
| closing_balance | float|null | |
| total_credited | float|null | Sum of incoming transactions |
| total_debited | float|null | Negative value |
Transaction object
Each item in transactions[].
Unused fields are null.
Debit amounts are negative floats. Dates are ISO YYYY-MM-DD.
| Field | Type | Notes |
|---|---|---|
| date_posted | string|null | Accounting date |
| date_valuta | string|null | Value date |
| amount | float|null | Negative = debit, positive = credit |
| currency | string | Always "CZK" |
| type | string|null | Transaction category from the bank |
| description | string|null | Payment message / note |
| counterparty_account | string|null | E.g. 987654321/0600 |
| counterparty_name | string|null | |
| vs | string|null | Variable symbol |
| ks | string|null | Constant symbol |
| ss | string|null | Specific symbol |
| fee | float|null | Transaction fee (Air Bank only) |
| transaction_id | string|null | Bank-assigned transaction ID |
| balance_after | float|null | Running balance after this transaction |
| raw_line | string|null | Raw PDF text line (debug only) |
| tags | string[] | Semantic tags — see below |
Example transaction
{
"date_posted": "2026-01-05",
"date_valuta": "2026-01-05",
"amount": 35000.00,
"currency": "CZK",
"type": "Příchozí úhrada",
"description": "Mzda leden 2026",
"counterparty_account": "987654321/0600",
"counterparty_name": "Acme s.r.o.",
"vs": "20260105",
"ks": "0308",
"ss": null,
"fee": null,
"transaction_id": "20260105001",
"balance_after": 47500.00,
"raw_line": null,
"tags": ["salary"]
}
Signature object
Returned as result.signature.
Every parse result includes this object — when no digital signature is embedded in the PDF,
verdict is
"unsigned" and every other field is null.
The verifier never raises — the worst case is verdict: "error"
with a human‑readable entry in warnings[].
Verdicts
| Verdict | Meaning |
|---|---|
valid |
Intact, signer matches detected bank, cert chain rooted in a trusted CA |
valid_untrusted |
Intact, signer matches detected bank, but cert chain is not in our local trust store |
valid_unknown_bank |
Intact, but the bank could not be detected from the PDF content to compare the signer against |
signer_mismatch |
Intact, but the certificate's organisation / IČO points to a different bank than what the content says — strong fraud signal |
tampered |
File bytes were modified after signing, or content was appended past the signed region (coverage=partial) |
unsigned |
No embedded signature. Expected for Air Bank, ČSOB, mBank, UniCredit, Partners Banka. |
error |
Verification threw an exception — see warnings[] for details |
Fields
| Field | Type | Notes |
|---|---|---|
| present | boolean | true if a signature is embedded in the PDF |
| verdict | string | See table above |
| intact | boolean|null | Cryptographic integrity — true if bytes have not been altered since signing |
| coverage | string|null | entire_file, entire_revision (legitimate — bank added annotations after signing; Česká spořitelna always), or partial (content appended after signature — red flag) |
| bank_match | boolean|null | true when signer's NTRCZ‑IČO (or organisation) points to the same bank as detected from PDF content |
| trusted | boolean|null | true when the cert chain ends at a root in our trust store |
| trust_error | string|null | Why trusted is false (missing root CA, revoked cert, SHA‑1 legacy hash …) |
| signer | object|null | Subject fields of the signing cert — subject_cn, organization, ntrcz_id, country, issuer_cn, issuer_organization |
| signed_at | string|null | Declared signing time (ISO 8601 with timezone). Cryptographically attested only when timestamp_attested=true. |
| timestamp_attested | boolean|null | true if the signature carries a verified RFC 3161 timestamp token from a trusted TSA |
| subfilter | string|null | PDF signature SubFilter — adbe.pkcs7.detached (most banks), adbe.pkcs7.sha1 (FIO legacy), ETSI.CAdES.detached (PAdES) |
| hash_algorithm | string|null | Digest algorithm — sha256, sha512, or sha1 (legacy, FIO only) |
| warnings | string[] | Non‑fatal advisories: legacy_sha1_signature, expected_signed_but_unsigned, signer_bank_mismatch, multi_sig_ignored:N, error:<detail> |
Per‑bank signing behaviour
Based on a 500‑file scan of real statements. For banks that never sign, an
unsigned verdict is not a red flag.
| Behaviour | Banks |
|---|---|
| Always signs | KB, Česká spořitelna, Raiffeisenbank, Fio banka |
| Sometimes signs (~67 %) | MONETA Money Bank |
| Never signs | Air Bank, ČSOB, mBank, UniCredit, Partners Banka |
Example — valid signature (KB)
{
"present": true,
"verdict": "valid",
"intact": true,
"coverage": "entire_file",
"bank_match": true,
"trusted": true,
"trust_error": null,
"signer": {
"subject_cn": "Elektronická pečeť Komerční banky, a.s.",
"organization": "Komerční banka, a.s.",
"ntrcz_id": "NTRCZ-45317054",
"country": "CZ",
"issuer_cn": "Komerční banka Qualified CA/RSA",
"issuer_organization": "Komerční banka, a.s."
},
"signed_at": "2026-03-25T21:01:07+00:00",
"timestamp_attested": false,
"subfilter": "adbe.pkcs7.detached",
"hash_algorithm": "sha256",
"warnings": []
}
Example — unsigned (Air Bank)
{
"present": false,
"verdict": "unsigned",
"intact": null,
"coverage": null,
"bank_match": null,
"trusted": null,
"trust_error": null,
"signer": null,
"signed_at": null,
"timestamp_attested": null,
"subfilter": null,
"hash_algorithm": null,
"warnings": []
}
Example — tampered
{
"present": true,
"verdict": "tampered",
"intact": false,
"coverage": "partial",
"bank_match": true,
"trusted": false,
"trust_error": "signature broken or data appended after signing",
"signer": {
"organization": "Raiffeisenbank a.s.",
"ntrcz_id": "NTRCZ-49240901",
"country": "CZ"
},
"signed_at": "2026-03-01T16:48:59+01:00",
"timestamp_attested": false,
"subfilter": "adbe.pkcs7.detached",
"hash_algorithm": "sha256",
"warnings": []
}
Upload a photo of a Czech identity card (Občanský průkaz) and receive structured JSON with extracted fields. The OCR pipeline (Tesseract + RapidOCR) runs entirely on our own servers — your images are never forwarded to a third-party AI provider. Handles rotated images, both card generations (pre-2012 and current), front and back sides.
Request
Two content types are accepted:
Option A —
Content-Type: application/json
| Field | Type | Description |
|---|---|---|
| filerequired | string | Image file encoded as Base64 |
| filenameoptional | string | Original filename (must end with .jpg, .jpeg, .png, or .webp). Default: upload.jpg |
Option B —
Content-Type: multipart/form-data
| Field | Type | Description |
|---|---|---|
| filerequired | file | JPG / PNG / WEBP image, max 20 MB |
Example — cURL (Base64 JSON)
B64=$(base64 -w 0 id_card_front.jpg) curl -X POST https://api.ginibooster.cz/api/v1/id_parse \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d "{\"file\":\"$B64\",\"filename\":\"id_card_front.jpg\"}"
Example — cURL (multipart)
curl -X POST https://api.ginibooster.cz/api/v1/id_parse \ -H "Authorization: Bearer YOUR_API_KEY" \ -F "file=@id_card_front.jpg"
Example — Python (Base64 JSON)
import base64, requests with open("id_card_front.jpg", "rb") as f: b64 = base64.b64encode(f.read()).decode() resp = requests.post( "https://api.ginibooster.cz/api/v1/id_parse", headers={"Authorization": "Bearer YOUR_API_KEY"}, json={"file": b64, "filename": "id_card_front.jpg"}, ) print(resp.json())
Response — 200 OK (front side)
{
"file_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890.jpg",
"result": {
"document_type": "cz_id",
"side": "front",
"side_confidence": 0.97,
"extraction_method": "local",
"front": {
"document_number": "AB1234567",
"surname": "NOVÁK",
"given_names": "JAN",
"date_of_birth": "12.05.1990",
"sex": "M",
"place_of_birth": "PRAHA",
"nationality": "ČESKÁ REPUBLIKA",
"date_of_issue": "01.06.2020",
"date_of_expiry": "01.06.2030",
"personal_number": "900512"
},
"back": null,
"confidence": {
"document_number": 1.00,
"surname": 0.98,
"given_names": 0.97,
"date_of_birth": 0.94
}
}
}
Response — 200 OK (back side)
{
"file_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890.jpg",
"result": {
"document_type": "cz_id",
"side": "back",
"side_confidence": 0.95,
"extraction_method": "local",
"front": null,
"back": {
"address": "Praha, Nové Město, Vzorová 1234/5",
"personal_number": "900512/1234",
"marital_status": "ženatý",
"mrz_line1": "IDCZEAB1234567<<<<<<<<<<<<<<<<<<<<<<<<0",
"mrz_line2": "9005121M3005121CZE<<JAN"
},
"mrz_extracted": {
"document_number": "AB1234567",
"date_of_birth": "12.05.1990",
"date_of_expiry": "12.05.2030",
"sex": "M",
"nationality": "CZE",
"valid": true
}
}
}
Response — 200 (not a Czech ID)
If the image is not recognised as a Czech ID, the API still returns 200 OK with document_type: "unknown" and empty front/back fields.
{
"file_id": "...",
"result": {
"document_type": "unknown",
"side": "unknown",
"front": null,
"back": null
}
}
ID document object
Top-level shape returned by /api/v1/id_parse.
Top-level fields
| Field | Type | Values |
|---|---|---|
| document_type | string | "cz_id" · "unknown" |
| side | string | "front" · "back" · "unknown" |
| side_confidence | number | 0.0–1.0 — confidence of the side classification |
| extraction_method | string | "local" — fields extracted on our servers (Tesseract + RapidOCR) |
| front | object|null | Populated when side = "front" |
| back | object|null | Populated when side = "back" |
| confidence | object | Per-field confidence scores (0.0–1.0) for fields the OCR returned |
| mrz_extracted | object|null | Back side only — fields recovered algorithmically from the MRZ zone with ICAO 9303 checksum verification (document_number, date_of_birth, date_of_expiry, sex, nationality, valid) |
front object
| Field | Type | Notes |
|---|---|---|
| document_number | string|null | 9-digit number (ČÍSLO DOKLADU) |
| surname | string|null | PŘÍJMENÍ |
| given_names | string|null | JMÉNO |
| date_of_birth | string|null | DD.MM.YYYY |
| sex | string|null | "M" or "F" |
| place_of_birth | string|null | MÍSTO NAROZENÍ |
| nationality | string|null | STÁTNÍ OBČANSTVÍ |
| date_of_issue | string|null | DD.MM.YYYY (DATUM VYDÁNÍ) |
| date_of_expiry | string|null | DD.MM.YYYY (PLATNOST DO) |
| personal_number | string|null | 6-digit partial rodné číslo shown on front |
back object
| Field | Type | Notes |
|---|---|---|
| address | string|null | Full address (street, city, district) |
| personal_number | string|null | Full rodné číslo from MRZ, e.g. 900512/1234 |
| marital_status | string|null | Rodinný stav if visible |
| mrz_line1 | string|null | First MRZ line (starts with IDCZE) |
| mrz_line2 | string|null | Second MRZ line |