Bank statement to JSON API: one request, with CSV

Send a bank statement PDF and a schema, get typed transactions back as JSON. One curl request, the response, a CSV conversion and a balance check.

4 min read

A bank statement is a PDF with a table in it, and the table is the whole point. This is one request that turns a statement into JSON with a schema you control, and a few lines that turn that JSON into a CSV. It is a narrower take than the general PDF to JSON guide.

The request

Send the statement and a JSON Schema describing the rows you want back:

bash
curl -X POST https://veralens.ai/api/v1/jobs/create \
  -H "Authorization: Bearer vl_your_secret_key" \
  -H "Content-Type: application/json" \
  -d '{
    "sources": [{ "type": "document", "data": "data:application/pdf;base64,JVBERi0..." }],
    "schema": {
      "type": "object",
      "properties": {
        "account_holder": { "type": "string" },
        "iban": { "type": "string", "format": "iban" },
        "currency": { "type": "string", "format": "currency" },
        "opening_balance": { "type": "number" },
        "closing_balance": { "type": "number" },
        "transactions": {
          "type": "array",
          "items": {
            "type": "object",
            "properties": {
              "date": { "type": "string", "format": "date" },
              "description": { "type": "string" },
              "amount": { "type": "number" }
            },
            "required": ["date", "description", "amount"]
          }
        }
      },
      "required": ["currency", "transactions"]
    }
  }'

Replace the data URL with your own base64 PDF. The call returns 201 straight away with the job in pending. Extraction runs asynchronously, so pass a webhook_url to be called when it finishes, or poll GET /jobs/:jobId/get every 2 to 3 seconds. See Jobs and queue.

The response

When the job is completed, output is a JSON string. Parse it:

json
{
  "account_holder": "Example Ltd",
  "iban": "DE89370400440532013000",
  "currency": "EUR",
  "opening_balance": 2310.4,
  "closing_balance": 1184.9,
  "transactions": [
    { "date": "2026-09-02", "description": "Card payment, Office supplies", "amount": -64.5 },
    { "date": "2026-09-03", "description": "Transfer from client", "amount": 1200 }
  ]
}

That output is illustrative, not from a real account. What the schema guarantees is the shape: numbers come back as numbers whatever separator the bank used, and format: "date" and format: "iban" make ISO dates and a validated IBAN a guarantee rather than a judgement. See Schemas and formats.

A statement can run to many pages, and the transactions table does not stop at page one. A job takes a PDF up to 200 pages and 20 MB and answers it as one document.

Statement PDF to CSV

JSON to CSV is your code, not an API feature, and it is short:

ts
const { transactions } = JSON.parse(job.output)

const rows = transactions.map(
  (t: { date: string; description: string; amount: number }) =>
    [t.date, JSON.stringify(t.description), t.amount].join(","),
)

const csv = ["date,description,amount", ...rows].join("\n")

Check it reconciles

Do not trust a statement import until it adds up. The statement already carries the check: opening balance plus the sum of the transactions should equal the closing balance.

ts
const sum = transactions.reduce((n: number, t: { amount: number }) => n + t.amount, 0)
const ok = Math.abs(opening_balance + sum - closing_balance) < 0.005

When it does not, route that statement to review rather than importing it. Tables are the hardest part of any document, and a split or merged row is where every extraction system is weakest. Keep required short so a field that is genuinely absent comes back null instead of invented. More on this in extracted totals that reconcile.

Try it

  1. Create an account and an API key.
  2. Add funds in Billing. There is no free balance, so top up before your first job. Billing is prepaid per token, you can add from $5, and a failed job refunds its hold in full. See pricing.
  3. Run the request above on one of your own statements.

Get your API key and run your first statement.

Try it on your own documents

Add funds from $5, create a key, and send your first document. Every job reports exactly what it cost.

Add $5, get an API key

Keep reading