Statement parsing APIfor Nigerian banks
For lending fintechs, bookkeeping SaaS, and developers who need clean transaction data out of Nigerian bank statements. Without building a parser.
Open source. Self-host. Cloud-hosted.
curl -X POST https://bankstract.logickoder.dev/v1/parse \
-H "Authorization: Bearer bsk_live_..." \
-F "pdf=@statement.pdf"Full reference in the docs.
Integrate today
One endpoint. Your language. Same JSON contract every time.
curl -X POST https://bankstract.logickoder.dev/v1/parse \
-H "Authorization: Bearer bsk_live_..." \
-F "pdf=@statement.pdf"import requests
with open("statement.pdf", "rb") as pdf:
res = requests.post(
"https://bankstract.logickoder.dev/v1/parse",
headers={"Authorization": "Bearer bsk_live_..."},
files={"pdf": pdf},
)
data = res.json()
print(data["metadata"]["bank"], len(data["transactions"]))const form = new FormData()
form.append("pdf", file)
const res = await fetch("https://bankstract.logickoder.dev/v1/parse", {
method: "POST",
headers: { Authorization: "Bearer bsk_live_..." },
body: form,
})
const data = await res.json()
console.log(data.metadata.bank, data.transactions.length)body := &bytes.Buffer{}
w := multipart.NewWriter(body)
part, _ := w.CreateFormFile("pdf", "statement.pdf")
io.Copy(part, file)
w.Close()
req, _ := http.NewRequest("POST", "https://bankstract.logickoder.dev/v1/parse", body)
req.Header.Set("Authorization", "Bearer bsk_live_...")
req.Header.Set("Content-Type", w.FormDataContentType())
res, _ := http.DefaultClient.Do(req)What you get
Parse
Bank PDF in, structured JSON out. Transactions, balances, and account metadata, fully typed. One endpoint, the same contract every time.
{
"metadata": { "bank": "fbn", "account_number_masked": "****1234" },
"transactions": [
{ "date": "2026-06-01", "narration": "Transfer FOO", "credit": "250.00", "balance": "1250.00" },
{ "date": "2026-06-02", "narration": "POS ACME", "debit": "40.00", "balance": "1210.00" }
]
}Redact
NDPR-aware redaction in the same call. Names, account numbers, and BVN masked in place.
Reconcile
Every parse runs a balance check. Totals that do not line up fail loud, never silent.
Fails noisy
{ "error_class": "ReconciliationError",
"error": "row totals do not match header" }Reconciliation invariant
Every parse checks row balances against the statement totals. A mismatch returns a 422, not a wrong number.
{ "error_class": "LayoutDriftError",
"format_version": "zenith-2025-11" }Format-version drift detection
When a bank changes its layout, you get an explicit error with the format version, never a silent dropout.
buf = io.BytesIO(await pdf.read())
result = bankstract.parse(buf)Stream input
PDF bytes flow through memory. No temp files, no disk writes, BytesIO end to end.
Reads Naija banks first
4 banks parse today. The rest are on the roadmap, in the open.
Format drift fixed in 48h.
Compliance
NDPR by default.
Privacy is the product. Statements are parsed in memory and dropped. The audit log keeps metadata, never contents. Every claim is verifiable in the open source.
Read the security policy or run the MIT engine on-prem.
- 01NDPR-aware redaction in one API call.
- 02Ephemeral processing. We never write your PDF to disk.
- 03Audit log is metadata only. Filename and byte count, never contents.
- 04Source on GitHub. Verify the claims yourself.
- 05On-prem option. Run the MIT engine on your own infrastructure instead.
We're open source. AGPL-3.0.
The cloud layer and the engine are both public. Run the whole stack yourself with docker-compose. No license fee, no lock-in.
Need a bank added? Sponsor the implementation.
Built by logickoder.
git clone https://github.com/logickoder/bankstract-cloud
cd bankstract-cloud/infra
docker compose up --buildParse your first statement
Free demo, no signup. Self-host the whole stack, or use the cloud when paid tiers ship.
Need volume or NDPR audit support? Talk to sales.