Overview
Base URL: https://tool.accohub.in/api/v1. All requests use HTTPS and return JSON (except file downloads). Conversion is asynchronous:
- Upload a statement → you get an
id. - Poll its status until it is
done(usually a few seconds; scanned statements take longer). - Fetch the transactions as JSON, or download Excel / CSV. Credits are used once, on the first fetch.
Authentication
Create a key on your Account page (needs an active Saver Combo). The full key is shown once — store it in a secret manager. Send it in the Authorization header:
Authorization: Bearer acc_1a2b3c4d5e6f_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
You can have up to 3 active keys and revoke any of them instantly. Never put a key in a browser, mobile app or public repository — call the API from your server.
Quick start
# 1. upload
curl -X POST https://tool.accohub.in/api/v1/statements \
-H "Authorization: Bearer $ACCOHUB_KEY" \
-F "file=@statement.pdf" -F "password=" -F "doc_type=auto"
# → {"id":"Gx7…","status":"queued"}
# 2. poll
curl https://tool.accohub.in/api/v1/statements/Gx7… -H "Authorization: Bearer $ACCOHUB_KEY"
# 3. transactions as JSON (uses credits once)
curl https://tool.accohub.in/api/v1/statements/Gx7…/transactions -H "Authorization: Bearer $ACCOHUB_KEY"
import os, time, requests
API = "https://tool.accohub.in/api/v1"
H = {"Authorization": f"Bearer {os.environ['ACCOHUB_KEY']}"}
with open("statement.pdf", "rb") as f:
job = requests.post(f"{API}/statements", headers=H, files={"file": f}).json()
while True:
s = requests.get(f"{API}/statements/{job['id']}", headers=H).json()
if s["status"] in ("done", "error"):
break
time.sleep(2)
data = requests.get(f"{API}/statements/{job['id']}/transactions", headers=H).json()
print(len(data["transactions"]), "rows")
import fs from "node:fs";
const API = "https://tool.accohub.in/api/v1";
const H = { Authorization: `Bearer ${process.env.ACCOHUB_KEY}` };
const form = new FormData();
form.append("file", new Blob([fs.readFileSync("statement.pdf")]), "statement.pdf");
const job = await (await fetch(`${API}/statements`, { method: "POST", headers: H, body: form })).json();
let s;
do {
await new Promise((r) => setTimeout(r, 2000));
s = await (await fetch(`${API}/statements/${job.id}`, { headers: H })).json();
} while (s.status === "queued" || s.status === "running");
const data = await (await fetch(`${API}/statements/${job.id}/transactions`, { headers: H })).json();
console.log(data.transactions.length, "rows");
Limits
| Limit | Value | When exceeded |
|---|---|---|
| Requests per key | 30 per minute | 429 rate_limited with Retry-After |
| Conversions running at once | 3 per key | 429 too_many_running |
| Credits per day | 1,000 per account | 429 daily_limit |
| Upload size | 25 MB per request | 413 too_large |
| Pages per statement | 150 | status: error |
| Invalid keys from one network | 20 in 15 minutes | 429 blocked for 15 minutes |
Need higher limits? Email us.
Credits & billing
One credit converts one statement page (spreadsheet/CSV statements: one credit per 60 rows). A conversion is charged once, the first time you fetch its transactions or file — failed conversions are never charged. Credits are the same ones you buy on the pricing page; with an active Saver Combo they cost ₹1 each. Free statements are for the website only.
GET /account
Your credit balance, plan and limits.
{"credits": 248, "plan": "pro", "plan_valid_until": "2026-11-24T10:01:00",
"limits": {"requests_per_minute": 30, "concurrent_conversions": 3, "credits_per_day": 1000,
"credits_used_today": 12, "max_file_mb": 25, "result_kept_minutes": 30}}
POST /statements
multipart/form-data with:
| Field | Required | Description |
|---|---|---|
file | Yes | The statement: PDF (digital or scanned), JPG/PNG/TIFF photo, XLSX/XLS or CSV. For several photos of one statement, repeat file. |
password | No | Password of a protected PDF. Used once and discarded. |
doc_type | No | auto (default), bank or cc (credit card). |
date_order | No | auto (default), dmy or mdy. |
Returns 202 {"id": "…", "status": "queued"}.
GET /statements/{id}
Status is queued, running, done or error. When done:
{"id": "Gx7…", "status": "done", "pages": 4, "credits_required": 4, "rows": 61,
"document_type": "bank", "charged": false, "expires_in_seconds": 1712}
GET /statements/{id}/transactions
All rows as JSON (uses credits the first time). check is verified (ties to the printed running balance), adjusted (we corrected it — see the website review for evidence) or review (please check).
{"id": "Gx7…", "document_type": "bank", "pages": 4, "opening_balance": 50000.0,
"statement": {"bank": "…", "account_holder": "…", "period_from": "2026-04-01", "period_to": "2026-06-30"},
"summary": {"debit": 182340.5, "credit": 201000.0, "review": 0, "adjusted": 1},
"transactions": [{"date": "2026-04-02", "value_date": "2026-04-02", "narration": "SALARY APR",
"reference": "8161424…", "debit": null, "credit": 85000.0, "balance": 135000.0, "page": 1,
"check": "verified"}]}
GET /statements/{id}/file?format=xlsx|csv
The converted file — Excel with Transactions, Summary and Validation sheets, or a UTF-8 CSV. Uses credits the first time (shared with /transactions).
DELETE /statements/{id}
Erases the result immediately. Results are erased automatically after 30 minutes anyway.
Errors
Errors return a JSON body {"error": {"code": "…", "message": "…"}}.
| HTTP | code | Meaning |
|---|---|---|
| 400 | bad_request, no_file, unsupported | Fix the request. |
| 401 | unauthorized | Missing, invalid or revoked key. |
| 402 | no_credits | Buy credits; the response includes required and balance. |
| 403 | plan_required | The account's plan has ended. |
| 404 | not_found | Unknown id, or already expired/deleted. |
| 409 | not_ready | Still converting — poll again. |
| 413 | too_large | File over the size limit. |
| 429 | rate_limited, too_many_running, daily_limit, blocked | Wait for Retry-After seconds. |
| 503 | busy | Temporarily busy — retry with back-off. |
Security best practices
- Keep keys on your server; rotate them by creating a new key and revoking the old one.
- Use one key per system so you can revoke one without touching the others.
- Retry
429and503with exponential back-off; don't hammer the API. - Delete results you've stored as soon as you've imported them.
Data retention
Uploaded files are deleted as soon as reading finishes. Results are kept only for 30 minutes (or until you call DELETE) so you can fetch them, then erased. We don't keep statement contents. See our Privacy Policy.