Accountant plan required

API Reference

The LedgerLift API lets you parse bank and credit card statement PDFs programmatically and receive structured JSON transactions — the same extraction that powers the UI.

Overview

The API exposes a single endpoint. You POST a statement file (PDF, PNG, JPG, or CSV) and receive a JSON array of transactions with dates, amounts, descriptions, and auto-detected categories.

Any bank or card
Chase, Amex, Citi, BofA, and 200+ more — no templates.
Seconds, not minutes
Most statements process in under 30 seconds.
99%+ accuracy
Digital PDFs. Review and correct via the UI if needed.

Authentication

All API requests require a Bearer token in the Authorization header. API keys are generated in your account settings and require the Accountant plan.

http
Authorization: Bearer ll_a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0

Keep your API key secret. Never expose it in client-side JavaScript or public repositories. Keys can be revoked at any time from your account settings.

Endpoint

POST/api/v1/parse

Parses a bank or credit card statement and returns structured transactions. The file is processed by Claude AI and the page count is deducted from your monthly quota.

Request

Send a multipart/form-data request with the following fields:

FieldTypeDescription
filerequiredFileThe statement file. Accepted types: PDF, PNG, JPG, CSV. Max size: 50 MB.
webhook_urlstringOptional URL to receive a POST callback when parsing finishes. On success the payload is the full response body plus event: 'parse.completed'; on failure it is { event: 'parse.failed', jobId, error }.

Headers

HeaderValueNotes
AuthorizationrequiredstringBearer <your_api_key>
Content-TypestringSet automatically by your HTTP client when sending multipart/form-data. Do not set this manually.

Response

A successful request returns 200 OK with a JSON body:

json
{
  "transactions": [
    {
      "id": "job123-0",
      "date": "2024-03-05",
      "description": "AMAZON WEB SERVICES",
      "debit": 87.43,
      "credit": null,
      "balance": 18345.07,
      "category": "software"
    },
    {
      "id": "job123-1",
      "date": "2024-03-10",
      "description": "ACH PAYROLL GUSTO INC",
      "debit": 6500.00,
      "credit": null,
      "balance": 11845.07,
      "category": "payroll"
    },
    {
      "id": "job123-2",
      "date": "2024-03-18",
      "description": "WIRE TRANSFER RECEIVED CLIENT PMT",
      "debit": null,
      "credit": 7500.00,
      "balance": 19345.07,
      "category": "income"
    }
  ],
  "bankDetected": "Chase Business Checking",
  "pageCount": 3,
  "rowCount": 47,
  "jobId": "0f9d2c1e-..."
}

Response fields

FieldTypeDescription
transactionsTransaction[]Array of extracted transactions. See Transaction object below.
bankDetectedstringBank or card issuer name detected from the document.
pageCountnumberNumber of pages in the document. Deducted from your monthly quota.
rowCountnumberNumber of transactions extracted. Equal to transactions.length.
jobIdstringIdentifier of the conversion job created for this request. Also included in webhook callbacks.

Transaction object

FieldTypeDescription
idstringUnique identifier for this transaction within the response.
datestringTransaction date in YYYY-MM-DD format.
descriptionstringCleaned merchant or payee description.
debitnumber | nullAmount debited (money out). Null if not a debit.
creditnumber | nullAmount credited (money in). Null if not a credit.
balancenumber | nullRunning balance after transaction. Null if not present in source document.
categorystringAuto-detected category. See categories below.

Categories

The category field will be one of:

incomepayrolltransferutilitiessoftwareadvertisingtravelmealssuppliesrentinsurancetaxesfeesother

Error codes

All errors return a JSON object with an error field describing what went wrong.

json
{ "error": "Monthly page limit reached." }
StatusCauseFix
401Missing or invalid API keyCheck your Authorization header and ensure the key is correct.
400No file provided, or unsupported file typeAttach a PDF, PNG, JPG, or CSV file to the form-data field named "file".
400File exceeds 50 MBSplit large statements into smaller files.
402Monthly page quota exhaustedWait for quota reset on the 1st, or contact support for a higher limit.
403Account is not on the Accountant planUpgrade to the Accountant plan to use the API.
500AI processing failedRetry the request. If it persists, check that the file is not corrupted or password-protected.

Limits

LimitValueNotes
Monthly pages5,000 pagesShared across UI and API. Resets on the 1st of each month.
Max file size50 MBPer request.
Request timeout120 secondsLarge multi-page PDFs may take longer. Retry if you receive a timeout.
Supported typesPDF, PNG, JPG, CSVDigital PDFs give the highest accuracy (99%+).

Code examples

cURL

bash
curl -X POST https://ledgerlift.me/api/v1/parse \
  -H "Authorization: Bearer ll_your_api_key_here" \
  -F "file=@/path/to/statement.pdf"

JavaScript / Node.js

javascript
const fs = require('fs')
const FormData = require('form-data')

async function parseStatement(filePath) {
  const form = new FormData()
  form.append('file', fs.createReadStream(filePath))

  const res = await fetch('https://ledgerlift.me/api/v1/parse', {
    method: 'POST',
    headers: {
      Authorization: 'Bearer ll_your_api_key_here',
      ...form.getHeaders(),
    },
    body: form,
  })

  if (!res.ok) {
    const { error } = await res.json()
    throw new Error(error)
  }

  const { transactions, bankDetected, rowCount } = await res.json()
  console.log(`${bankDetected}: ${rowCount} transactions`)
  return transactions
}

parseStatement('./chase_march_2024.pdf')

Python

python
import requests

def parse_statement(file_path: str, api_key: str) -> list[dict]:
    with open(file_path, 'rb') as f:
        response = requests.post(
            'https://ledgerlift.me/api/v1/parse',
            headers={'Authorization': f'Bearer {api_key}'},
            files={'file': f},
        )

    response.raise_for_status()
    data = response.json()

    print(f"{data['bankDetected']}: {data['rowCount']} transactions")
    return data['transactions']


transactions = parse_statement('statement.pdf', 'll_your_api_key_here')

# Filter to debits only
debits = [t for t in transactions if t['debit'] is not None]
total = sum(t['debit'] for t in debits)
print(f"Total spent: ${total:,.2f}")

TypeScript (with types)

typescript
interface Transaction {
  id: string
  date: string
  description: string
  debit: number | null
  credit: number | null
  balance: number | null
  category: string
}

interface ParseResponse {
  transactions: Transaction[]
  bankDetected: string
  pageCount: number
  rowCount: number
}

async function parseStatement(file: File, apiKey: string): Promise<ParseResponse> {
  const form = new FormData()
  form.append('file', file)

  const res = await fetch('https://ledgerlift.me/api/v1/parse', {
    method: 'POST',
    headers: { Authorization: `Bearer ${apiKey}` },
    body: form,
  })

  if (!res.ok) {
    const { error } = await res.json()
    throw new Error(error)
  }

  return res.json()
}
Ready to integrate?
Generate your API key from the account settings page.
Get your API key