API Reference
The Statement Pro API lets you convert PDF bank statements to structured data programmatically. Upload a PDF, poll for results, and download transactions in your preferred format.
Base URL
https://www.statementpro.app/api/v1/
All responses are JSON. For interactive testing, try the Swagger UI.
Authentication
Authenticate every request with a Bearer token in the Authorization header.
Authorization: Bearer sp_live_your_key_here
Managing API Keys
- Go to Account → API Keys
- Click Generate New Key
- Copy the key immediately — it won’t be shown again
Keep your key secret. Don’t commit it to version control or expose it in client-side code. Use environment variables instead.
Quick Start
Three steps: upload a PDF, poll for completion, download the results.
# 1. Upload a PDF
curl -X POST https://www.statementpro.app/api/v1/convert/ \
-H "Authorization: Bearer sp_live_your_key_here" \
-F "[email protected]"
# 2. Check status (replace {job_id} with the id from step 1)
curl https://www.statementpro.app/api/v1/jobs/{job_id}/ \
-H "Authorization: Bearer sp_live_your_key_here"
# 3. Download CSV
curl -O https://www.statementpro.app/api/v1/jobs/{job_id}/download/csv/ \
-H "Authorization: Bearer sp_live_your_key_here"
import time
import requests
API_KEY = "sp_live_your_key_here"
headers = {"Authorization": f"Bearer {API_KEY}"}
# 1. Upload a PDF
with open("statement.pdf", "rb") as f:
resp = requests.post(
"https://www.statementpro.app/api/v1/convert/",
headers=headers,
files={"file": f},
)
job = resp.json()
print(f"Job created: {job['id']}")
# 2. Poll for completion
while job["status"] in ("pending", "processing"):
time.sleep(2)
job = requests.get(
f"https://www.statementpro.app/api/v1/jobs/{job['id']}/",
headers=headers,
).json()
# 3. Get transactions
transactions = requests.get(
f"https://www.statementpro.app/api/v1/jobs/{job['id']}/transactions/",
headers=headers,
).json()
print(f"Found {len(transactions)} transactions")
const API_KEY = "sp_live_your_key_here";
const headers = { Authorization: `Bearer ${API_KEY}` };
// 1. Upload a PDF
const form = new FormData();
form.append("file", fileInput.files[0]);
let job = await fetch("https://www.statementpro.app/api/v1/convert/", {
method: "POST",
headers,
body: form,
}).then((r) => r.json());
// 2. Poll for completion
while (["pending", "processing"].includes(job.status)) {
await new Promise((r) => setTimeout(r, 2000));
job = await fetch(
`https://www.statementpro.app/api/v1/jobs/${job.id}/`,
{ headers }
).then((r) => r.json());
}
// 3. Get transactions
const transactions = await fetch(
`https://www.statementpro.app/api/v1/jobs/${job.id}/transactions/`,
{ headers }
).then((r) => r.json());
Endpoints
/api/v1/convert/
Upload a PDF bank statement for conversion. Returns a job object with status pending.
Request Body (multipart/form-data)
| Field | Type | Description |
|---|---|---|
| file | file required | The PDF bank statement to convert |
| webhook_url | string | URL to receive a POST when the job completes or fails |
Response 202 Accepted
{
"id": "abc123",
"status": "pending",
"created_at": "2026-02-19T10:30:00Z",
"file_name": "statement.pdf",
"pages": 3
}
/api/v1/jobs/{id}/
Retrieve the current status of a conversion job.
Response Fields
| Field | Type | Description |
|---|---|---|
| id | string | Unique job identifier |
| status | string | pending | processing | completed | failed |
| file_name | string | Original uploaded file name |
| pages | integer | Number of pages in the PDF |
| bank_name | string | null | Detected bank name, or null if AI-parsed |
| transaction_count | integer | Number of extracted transactions (0 while processing) |
| created_at | datetime | ISO 8601 timestamp |
| error_message | string | null | Error details if status is failed |
Response 200 OK
{
"id": "abc123",
"status": "completed",
"file_name": "statement.pdf",
"pages": 3,
"bank_name": "Chase",
"transaction_count": 47,
"created_at": "2026-02-19T10:30:00Z",
"error_message": null
}
/api/v1/jobs/{id}/transactions/
Retrieve extracted transactions for a completed job.
Response 200 OK
[
{
"date": "2026-01-15",
"description": "DIRECT DEPOSIT - ACME CORP",
"amount": 3250.00,
"type": "credit",
"balance": 5432.10
},
{
"date": "2026-01-16",
"description": "WHOLEFDS MKT #10245",
"amount": -87.32,
"type": "debit",
"balance": 5344.78
}
]
/api/v1/jobs/{id}/download/{format}/
Download the converted statement in the specified format. Returns the file as a binary download.
Path Parameters
| Parameter | Options | Description |
|---|---|---|
| format | csv xlsx json ofx qif |
Export format for the download |
/api/v1/usage/
Check your current billing period usage and plan limits.
Response 200 OK
{
"tier": "professional",
"pages_used": 42,
"pages_limit": 200,
"pages_remaining": 158,
"credit_pages": 10,
"billing_period_end": "2026-03-01T00:00:00Z"
}
Webhooks
Instead of polling, pass a webhook_url when creating a conversion.
We’ll send a POST request to that URL when the job completes or fails.
Setup
curl -X POST https://www.statementpro.app/api/v1/convert/ \
-H "Authorization: Bearer sp_live_your_key_here" \
-F "[email protected]" \
-F "webhook_url=https://your-app.com/webhooks/statement-pro"
Payload
The webhook POST body contains the event type and the full job object.
{
"event": "job.completed",
"job": {
"id": "abc123",
"status": "completed",
"file_name": "statement.pdf",
"pages": 3,
"bank_name": "Chase",
"transaction_count": 47,
"created_at": "2026-02-19T10:30:00Z"
}
}
{
"event": "job.failed",
"job": {
"id": "abc123",
"status": "failed",
"file_name": "statement.pdf",
"pages": 3,
"error_message": "No transactions found in the document",
"created_at": "2026-02-19T10:30:00Z"
}
}
Retry Policy
If your endpoint returns a non-2xx status, we retry up to 5 times with exponential backoff (1 min, 5 min, 30 min, 2 hr, 12 hr).
Security tip: Verify webhook authenticity by checking that the job ID matches one you created. You can also restrict incoming IPs or use a secret path segment in your webhook URL.
Rate Limits
API requests are rate-limited per hour based on your subscription tier.
| Tier | Requests / Hour |
|---|---|
| Free | 10 |
| Starter | 30 |
| Professional | 60 |
| Business | 200 |
When you exceed the limit, the API returns 429 Too Many Requests with a Retry-After header (seconds until the window resets).
{
"error": {
"code": "rate_limit_exceeded",
"message": "Rate limit exceeded. Retry after 42 seconds."
}
}
Error Handling
Errors return a consistent JSON envelope with an error object containing a machine-readable code and a human-readable message.
{
"error": {
"code": "invalid_file",
"message": "The uploaded file is not a valid PDF."
}
}
Error Codes
| HTTP Status | Code | Description |
|---|---|---|
| 400 | invalid_file | File is not a valid PDF or is corrupted |
| 400 | missing_file | No file was included in the request |
| 401 | unauthorized | Missing or invalid API key |
| 403 | usage_exceeded | Monthly page limit reached — upgrade or buy credits |
| 404 | not_found | Job ID does not exist or does not belong to you |
| 409 | job_not_ready | Tried to download results for a job still processing |
| 429 | rate_limit_exceeded | Too many requests — check the Retry-After header |
Export Formats
Pass the format slug in the download endpoint path. All formats include the same transaction data.
| Format | Slug | Description |
|---|---|---|
| CSV | csv | Comma-separated values — works with Excel, Google Sheets, and accounting apps |
| Excel | xlsx | Native Excel workbook with formatted columns |
| JSON | json | Structured JSON array of transaction objects |
| OFX | ofx | Open Financial Exchange — import into Quicken, GnuCash, and other financial software |
| QIF | qif | Quicken Interchange Format — legacy financial software support |