Formats & exportsUpdated 13 July 2026

Using the API

Everything the converter does is available over HTTPS: upload a statement, poll until it's parsed and balance-checked, then download the result in any format. Three endpoints, no SDK required.

Authentication

Owners and admins create keys in Settings → Team → API keys. The key is shown once — store it like a password, and send it as a bearer token on every request:

Authorization: Bearer nrk_vZxd79iwXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Keys belong to the team: conversions they create count against the team's page allowance and appear in the team's history, attributed to the API.

1. Upload a statement

curl https://norekey.com/api/v1/conversions \
  -H "Authorization: Bearer $NOREKEY_KEY" \
  -F "statement=@january.pdf"

Add -F "password=..." for a password-protected PDF — the password is used to open the file and never stored. You'll get 201 Created back immediately, before parsing starts:

{
    "id": "01kx90e4hpjjxhkrzb6fz1kja1",
    "statement": "january.pdf",
    "status": "pending",
    "pages": 1,
    "bank": null,
    "currency": null,
    "balanced": null,
    "opening_balance": null,
    "closing_balance": null,
    "error": null,
    "formats": ["csv", "xlsx", "ofx", "qfx", "json"],
    "created_at": "2026-07-14T09:12:03+00:00",
    "completed_at": null
}

Prefer JSON to multipart? Send the file base64-encoded — handy from serverless functions and no-code tools:

curl https://norekey.com/api/v1/conversions \
  -H "Authorization: Bearer $NOREKEY_KEY" \
  -H "Content-Type: application/json" \
  -d "{\"filename\": \"january.pdf\", \"statement\": \"$(base64 -i january.pdf)\"}"

Base64 inflates payloads by about a third, so multipart is the better choice for very large statements.

2. Poll until it's done

curl https://norekey.com/api/v1/conversions/01kx90e4hpjjxhkrzb6fz1kja1 \
  -H "Authorization: Bearer $NOREKEY_KEY"

Most statements finish in 10–30 seconds; polling every 2–3 seconds is plenty. When status is completed, the metadata is filled in — including balanced, the same verification the web converter runs:

{
    "id": "01kx90e4hpjjxhkrzb6fz1kja1",
    "statement": "january.pdf",
    "status": "completed",
    "pages": 1,
    "bank": "HSBC",
    "currency": "GBP",
    "balanced": true,
    "opening_balance": "3425.00",
    "closing_balance": "6320.09",
    "error": null,
    "formats": ["csv", "xlsx", "ofx", "qfx", "json"],
    "created_at": "2026-07-14T09:12:03+00:00",
    "completed_at": "2026-07-14T09:12:21+00:00"
}
Status Meaning
pending Accepted, waiting for a worker.
processing Being parsed and balance-checked right now.
completed Done — exports are ready. Check balanced.
failed Couldn't convert; error says why in plain English.
expired Rows passed your retention window; exports are gone.

A balanced of true means the rows reconcile exactly against the statement's own opening and closing balances. false means they don't — the data is still downloadable, but check it before importing. null means the statement declared no totals to verify against.

3. Download the export

The final path segment is the format — anything in the formats array works, and new formats join automatically:

curl -O -J https://norekey.com/api/v1/conversions/01kx90e4hpjjxhkrzb6fz1kja1/export/csv \
  -H "Authorization: Bearer $NOREKEY_KEY"

The json export is the richest: metadata, declared totals, the balance verdict and every row, with amounts as decimal strings so nothing is lost to float precision:

{
    "statement": "january.pdf",
    "bank": "HSBC",
    "currency": "GBP",
    "opening_balance": "3425.00",
    "closing_balance": "6320.09",
    "balanced": true,
    "transactions": [
        {
            "date": "2026-01-04",
            "description": "SAINSBURYS S/MKT",
            "amount": "-57.56",
            "balance": "3367.44"
        },
        {
            "date": "2026-01-24",
            "description": "BACS SALARY ACME LTD",
            "amount": "3597.17",
            "balance": "6964.61"
        }
    ]
}

A complete script

Python:

import requests, time

API = "https://norekey.com/api/v1"
HEADERS = {"Authorization": "Bearer nrk_..."}

with open("january.pdf", "rb") as f:
    conversion = requests.post(
        f"{API}/conversions", headers=HEADERS, files={"statement": f}
    ).json()

while conversion["status"] in ("pending", "processing"):
    time.sleep(2)
    conversion = requests.get(
        f"{API}/conversions/{conversion['id']}", headers=HEADERS
    ).json()

if conversion["status"] == "completed" and conversion["balanced"]:
    csv = requests.get(
        f"{API}/conversions/{conversion['id']}/export/csv", headers=HEADERS
    )
    open("january.csv", "wb").write(csv.content)
else:
    print("needs attention:", conversion["error"] or "totals need review")

Node:

const API = 'https://norekey.com/api/v1';
const headers = { Authorization: 'Bearer nrk_...' };

const form = new FormData();
form.append(
    'statement',
    new Blob([await fs.readFile('january.pdf')]),
    'january.pdf',
);

let conversion = await (
    await fetch(`${API}/conversions`, { method: 'POST', headers, body: form })
).json();

while (['pending', 'processing'].includes(conversion.status)) {
    await new Promise((r) => setTimeout(r, 2000));
    conversion = await (
        await fetch(`${API}/conversions/${conversion.id}`, { headers })
    ).json();
}

const rows = await (
    await fetch(`${API}/conversions/${conversion.id}/export/json`, { headers })
).json();
console.log(
    `${rows.transactions.length} transactions, balanced: ${rows.balanced}`,
);

Errors

Every error is JSON with a code and a human-readable message:

{
    "code": "quota_exceeded",
    "message": "This 3-page statement would take your team over its monthly page allowance."
}
Code Status When
unauthenticated 401 Missing, wrong or revoked key.
password_required 422 Protected PDF uploaded without its password.
invalid_statement 422 Base64 payload that isn't valid base64 or isn't a PDF.
too_many_pages 422 Over the per-file page cap.
quota_exceeded 422 The team's monthly page allowance is spent.
scanned_statement 422 A scanned PDF — detected and declined rather than guessed at.
404 Conversion doesn't exist or belongs to another team.
410 Conversion expired under your retention settings.

Limits, on every plan

API access isn't gated behind a paid tier — every team, Free included, can create keys and convert programmatically. Requests are limited to 120 per minute per key, and conversions consume your plan's page allowance exactly as web uploads do. Something missing for your integration? Tell us — support@norekey.com.

Was this guide helpful?
Got a statement to convert?
Free to try — no sign-up, balance-checked, deleted within the hour.
Convert a statement