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.
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.
Authorization: Bearer ll_a3f8b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0Keep 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
/api/v1/parseParses 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:
| Field | Type | Description |
|---|---|---|
filerequired | File | The statement file. Accepted types: PDF, PNG, JPG, CSV. Max size: 50 MB. |
webhook_url | string | Optional 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
| Header | Value | Notes |
|---|---|---|
Authorizationrequired | string | Bearer <your_api_key> |
Content-Type | string | Set 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:
{
"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
| Field | Type | Description |
|---|---|---|
transactions | Transaction[] | Array of extracted transactions. See Transaction object below. |
bankDetected | string | Bank or card issuer name detected from the document. |
pageCount | number | Number of pages in the document. Deducted from your monthly quota. |
rowCount | number | Number of transactions extracted. Equal to transactions.length. |
jobId | string | Identifier of the conversion job created for this request. Also included in webhook callbacks. |
Transaction object
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier for this transaction within the response. |
date | string | Transaction date in YYYY-MM-DD format. |
description | string | Cleaned merchant or payee description. |
debit | number | null | Amount debited (money out). Null if not a debit. |
credit | number | null | Amount credited (money in). Null if not a credit. |
balance | number | null | Running balance after transaction. Null if not present in source document. |
category | string | Auto-detected category. See categories below. |
Categories
The category field will be one of:
Error codes
All errors return a JSON object with an error field describing what went wrong.
{ "error": "Monthly page limit reached." }| Status | Cause | Fix |
|---|---|---|
| 401 | Missing or invalid API key | Check your Authorization header and ensure the key is correct. |
| 400 | No file provided, or unsupported file type | Attach a PDF, PNG, JPG, or CSV file to the form-data field named "file". |
| 400 | File exceeds 50 MB | Split large statements into smaller files. |
| 402 | Monthly page quota exhausted | Wait for quota reset on the 1st, or contact support for a higher limit. |
| 403 | Account is not on the Accountant plan | Upgrade to the Accountant plan to use the API. |
| 500 | AI processing failed | Retry the request. If it persists, check that the file is not corrupted or password-protected. |
Limits
| Limit | Value | Notes |
|---|---|---|
| Monthly pages | 5,000 pages | Shared across UI and API. Resets on the 1st of each month. |
| Max file size | 50 MB | Per request. |
| Request timeout | 120 seconds | Large multi-page PDFs may take longer. Retry if you receive a timeout. |
| Supported types | PDF, PNG, JPG, CSV | Digital PDFs give the highest accuracy (99%+). |
Code examples
cURL
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
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
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)
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()
}