Authentication
Send your key as a bearer token on every request to /v1/…. Requests without a valid key get HTTP 401.
Authorization: Bearer YOUR_KEY
Extract a statement
POST /v1/statements/extract with a JSON body. Send images (one base64 PNG or JPEG per page, in page order), text, or both.
curl -X POST https://YOUR_HOST/v1/statements/extract \
-H "Authorization: Bearer YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"images": ["<base64 page 1>", "<base64 page 2>"]}'
| Field | Type | Notes |
|---|---|---|
images | array of strings | Base64 PNG or JPEG page images, up to 50. The API does not accept PDFs: the web app converts PDF pages to images in the browser, and API users should render pages to images (about 1,500 px wide works well) before sending. |
text | string | Statement text, if you already have it. |
Response
{
"request_id": "9f2c…",
"backend": "openai",
"timing_ms": 61234,
"statement": {
"bank": "Northline Savings",
"account_last4": "3548",
"period_start": "2026-03-01",
"period_end": "2026-03-31",
"opening_balance": "6468.53",
"closing_balance": "8992.00",
"total_credits": "3777.12",
"total_debits": "1253.65",
"transactions": [
{ "date": "2026-03-03", "description": "RENT PAYMENT",
"amount": "-430.02", "balance": "6038.51" }
]
},
"validation": {
"status": "pass",
"risk_score": 0,
"flags": []
}
}
Money values are strings in dollars with two decimals. Amounts are negative for money leaving the account. A field the model could not find is null.
validation.status is pass or review. risk_score runs from 0 to 100. Each flag has a code, a severity (error or warn), a readable message, and a zero-based row index when it points at one transaction.
Check codes
Any error makes the status review. Warnings alone do not.
| Code | Severity | Meaning |
|---|---|---|
RECONCILIATION_MISMATCH | error | Opening balance plus transactions does not equal the closing balance. |
RUNNING_BALANCE_BREAK | error | A row's balance does not follow from the previous balance and its amount. Includes the row. |
SUMMARY_MISMATCH | error | Printed total deposits or withdrawals differ from the sum of the rows. |
MISSING_BALANCE, NO_TRANSACTIONS | error | Opening or closing balance, or all transactions, were not found. |
DATE_OUT_OF_PERIOD, DATE_ORDER, DATE_UNREADABLE | warn | A date falls outside the period, goes backwards, or could not be read. |
MODEL_OUTPUT_REPAIRED | warn | The model's reply ended early (often a missing closing brace) and was closed automatically. If rows were lost, the reconciliation check fails too. |
DUPLICATE_TRANSACTION | warn | Identical rows. Often legitimate, so it is only a warning. |
A review result means "a person should look". The checks cannot tell a misread digit from a deliberate edit, and an edit that keeps every balance consistent passes them.
Errors
Errors return JSON: {"error": "message"}.
| Status | Meaning |
|---|---|
| 400 | The body is not valid JSON. |
| 401 | Missing or wrong API key. |
| 413 | Body larger than 25 MB. |
| 422 | Bad input, for example no images or text, or invalid base64. |
| 502 | The model server failed or returned something unusable. Safe to retry. |
Limits
- Up to 50 page images per request and 25 MB per request.
- On a CPU-only server expect about a minute per page. Set a long client timeout (15 minutes is reasonable).
- Requests are processed one at a time by the model, so concurrent requests queue.
Health check
GET /health returns {"status": "ok", "backend": "…"} and needs no key.