Title: Batch Bank Statement Normalization for Accounting: A Controlled API Workflow Canonical URL: https://bankstatement.ai/blog/batch-process-bank-statements-accounting-api Updated: 2026-08-05 Category: accounting-embedded-workflows # Batch Bank Statement Normalization for Accounting: A Controlled API Workflow ## TL;DR - Use batch bank statement normalization for accounting only when every source remains independently traceable. - Create and poll a separate processing job for each accepted statement. - Quarantine failed or questionable statements without discarding successful work. - Review every completed result and combine only approved result IDs. - Move required results and exports into authorized storage before their seven-day expiry. Month-end processing becomes fragile when several statements are treated as one indivisible upload: a single failure can obscure successful work, while a questionable result can enter the final handoff without an explicit decision. A controlled pipeline preserves each statement’s identity, isolates failures, and requires review before any result moves downstream. ## Table of Contents - [Key Takeaways](#key-takeaways) - [Define the Batch Control Model Before You Submit Files](#define-the-batch-control-model-before-you-submit-files) - [Use a Manifest to Preserve Source-Level Traceability](#use-a-manifest-to-preserve-source-level-traceability) - [Run Each Statement as an Independent Asynchronous Job](#run-each-statement-as-an-independent-asynchronous-job) - [Work the Three-Statement Batch: Approve Two, Quarantine One](#work-the-three-statement-batch-approve-two-quarantine-one) - [Handle Page Capacity and Rate Limits Without Losing Batch State](#handle-page-capacity-and-rate-limits-without-losing-batch-state) - [Put a Human Review Gate Before Combination](#put-a-human-review-gate-before-combination) - [Choose JSON, Separate Exports, or One Reviewed XLSX](#choose-json-separate-exports-or-one-reviewed-xlsx) - [Complete the Handoff Before the Seven-Day Expiry](#complete-the-handoff-before-the-seven-day-expiry) - [FAQ](#faq) ## Key Takeaways - A batch is a collection of independently governed statements, jobs, and results—not one indivisible transaction. - Local source identifiers preserve traceability even when API jobs finish in a different order from submission. - Each job must retain its own terminal state; one failure must not redefine the state of the entire batch. - Completion begins the human approval process. It does not establish accounting suitability. - Only explicitly approved results should enter a combined export. - Expiry-aware transfer is part of the workflow, not an administrative task to postpone until later. ## Define the Batch Control Model Before You Submit Files Give the workflow a precise vocabulary before submitting anything. This prevents engineering, finance operations, and reviewers from using “processed” to describe different states. - **Source statement:** an authorized PDF, CSV, or XLSX document entering the workflow. - **Preflight decision:** the application-side decision to accept, reject, or hold a source before processing. Keep this conceptual unless you are implementing a separately documented preflight interface. - **Processing job:** the asynchronous unit created for one accepted statement. - **Terminal state:** either `complete` or `failed`. Reaching a terminal state ends polling for that job. - **Result ID:** the identifier associated with a completed processing result and used to retrieve or export it. - **Verification review:** a human assessment of whether a completed result is suitable for its intended handoff. - **Approved result set:** the result IDs that passed review and may proceed to export. - **Combined XLSX export:** one workbook created from selected result IDs for an operator handoff. - **Page allowance:** the processing capacity available to the account under its current plan. - **Rate-limit response:** an HTTP 429 response that temporarily delays an affected request and includes `retryAfterSeconds`. - **Seven-day expiry:** the deadline after which structured results, exports, and associated job metadata are deleted. The local source identifier, preflight disposition, review decision, approval state, and handoff status are controls in your application. They are not documented API request or response fields. This governance layer surrounds the API without extending its schema. ## Use a Manifest to Preserve Source-Level Traceability A manifest keeps source identity intact when jobs complete out of order. Create one record per statement before processing, then update that record as the statement moves through the pipeline. ![A source-linked manifest record preserves identifiers, decisions, status, and expiry through the workflow.](https://files.trafficwins.com/generated-images/0371334e-670e-4ffe-a66a-c61eca5d47f4/b187012c-aa9a-5bba-9d7f-a76a4a2ac3f4/d03f070a-2388-4e2d-b819-05c5ed356e10/da6095c1-3ddc-48ce-9e6b-4da3df6d533a/inline-1.png) A practical application-side manifest can contain: | Local field | Purpose | |---|---| | `sourceKey` | Stable identifier from your own records | | `authorizedFileRef` | Reference to the authorized source location | | `preflightStatus` | Local decision such as accepted, rejected, or held | | `jobId` | Job identifier returned after submission | | `terminalState` | Observed `complete` or `failed` state | | `resultId` | Result identifier recorded after completion | | `reviewStatus` | Local state such as pending, approved, or quarantined | | `handoffStatus` | Local state such as not ready, transferred, or closed | | `expiresAt` | Operational deadline based on result or export creation | These field names illustrate an application design; they are not bankstatement.ai API fields. Adapt them to your database and control conventions while preserving one invariant: every returned job ID and result ID must remain connected to its authorized source statement. Keep bearer credentials on the server. Browser and mobile clients should send requests through your backend instead of receiving the API token. That backend can also enforce manifest transitions so that a result cannot enter the approved set without a recorded review. Consult the [API documentation](https://bankstatement.ai/docs/api) for the documented interface and credential guidance. ## Run Each Statement as an Independent Asynchronous Job Create a separate processing job for every accepted source. According to the documented API behavior, successful job creation returns HTTP 202 with a job ID. Save that ID against the source immediately rather than waiting for every submission in the batch to finish. Run the lifecycle independently for each job: 1. Create a job for an accepted source statement. 2. Store the returned job ID in that statement’s manifest record. 3. Poll `GET /jobs/:id` until the job reaches `complete` or `failed`. 4. Retrieve a result only for a completed job. 5. Store its result ID against the same source. 6. Send that result to human review. Never let one job’s terminal state stand in for the entire batch. If four jobs are complete, one is still processing, and one has failed, the manifest should preserve those distinct states instead of reducing them to a generic “batch complete” or “batch failed” flag. A correction or retry should also remain traceable. Record it as a separate attempt associated with the same source rather than overwriting the failed job or resubmitting statements that already succeeded. Keep the API token on the server throughout job creation, polling, and result retrieval. ## Work the Three-Statement Batch: Approve Two, Quarantine One Consider this illustrative state flow. It contains no assumed transaction data, timing, output quality, or failure cause. ![Statements A and B enter the approved set after review; failed Statement C moves to quarantine.](https://files.trafficwins.com/generated-images/0371334e-670e-4ffe-a66a-c61eca5d47f4/b187012c-aa9a-5bba-9d7f-a76a4a2ac3f4/d03f070a-2388-4e2d-b819-05c5ed356e10/da6095c1-3ddc-48ce-9e6b-4da3df6d533a/inline-2.png) | Source | Terminal state | Review decision | Next action | |---|---|---|---| | Statement A | Complete | Approved | Add its result ID to the approved set | | Statement B | Complete | Approved | Add its result ID to the approved set | | Statement C | Failed | Not eligible for result review | Quarantine and investigate separately | Statements A and B remain usable even though Statement C failed. Their result IDs move forward only because each completed result passed its own review. Statement C retains its local source key and failed job ID so an authorized owner can investigate it without erasing the history of the attempt. Do not invent a reason for the failure when the available state does not provide one. Record what is known and keep the statement quarantined. Correction, resubmission, or exclusion from the current handoff is a separate decision that should be recorded against the same source. This is the operational value of controlled batching: partial success can continue without allowing a failure to disappear or enter the approved result set. ## Handle Page Capacity and Rate Limits Without Losing Batch State HTTP 402 and HTTP 429 require different responses. Treating both as generic retry errors can produce unnecessary requests and weaken control over the run. An **HTTP 402 response means more pages are needed**. Stop creating the affected work. The authorized owner must then decide whether to obtain adequate current capacity or reduce the run to a smaller authorized scope. Repeatedly submitting the same request does not resolve that capacity decision. An **HTTP 429 response is a temporary rate-limit response**. Read `retryAfterSeconds`, wait for the supplied interval, and retry the affected request. Preserve the manifest while waiting; completed jobs and other tracked jobs should not be discarded. Use this interruption policy: - On 402, pause affected new submissions and make a capacity or scope decision. - On 429, delay the affected request by `retryAfterSeconds` and then retry it. - In either case, preserve every existing source-to-job and job-to-result association. Before making a capacity decision, check the [current pricing page](https://bankstatement.ai/pricing) directly. Do not quote or hard-code plan prices or page allowances without confirming the current published information. ## Put a Human Review Gate Before Combination A job reaching `complete` means processing ended successfully. It does not mean the output is complete, accurate, approved, or suitable for accounting use. The [Terms of Service](https://bankstatement.ai/terms) assigns users responsibility for reviewing output before relying on it. Make one explicit decision for every completed result: - **Approve:** the result is accepted for the defined handoff and its result ID may enter the approved set. - **Quarantine:** the result is questionable or unsuitable and must remain outside downstream exports. - **Return for investigation:** additional source-level checking or correction is required before a decision can be made. The review method should match the destination and risk of the work. A verification summary does not replace comparison with an appropriate reference. For a structured approach to fixtures, expected results, and evaluation, use the published guide on [how to test a bank statement extraction API before you buy](https://bankstatement.ai/blog/how-to-test-a-bank-statement-extraction-api-before-you-buy). The batch controller’s responsibility is narrower: it must enforce the gate. No result ID should enter a combined-export request while its review state is pending, quarantined, or under investigation. Do not assume that processing performs automatic deduplication, reconciliation, or readiness-to-post checks. ## Choose JSON, Separate Exports, or One Reviewed XLSX Choose the output according to its next authorized consumer: - Use **normalized JSON** when your backend controls an embedded downstream workflow and needs structured data programmatically. - Use **separate CSV or XLSX files** when per-statement handling and source-level traceability remain important. - Use **one combined XLSX** when an operator needs a convenient workbook containing selected, reviewed results. The documented combined-workbook request is: ```http POST /api/v1/results/combined-exports/xlsx Content-Type: application/json { "resultIds": ["result_id_1", "result_id_2"] } ``` Populate `resultIds` only from the approved result set. A combined workbook is a packaging choice, not an accounting conclusion. It is not automatically deduplicated, reconciled, approved, integrated with a ledger, or ready to post. ## Complete the Handoff Before the Seven-Day Expiry Structured results and exports are available for seven days from creation. After expiry, the associated result records, export files, and job metadata are automatically deleted. Uploaded files are stored temporarily for processing and follow the documented cleanup lifecycle; this does not make the service a browsable statement archive. The [data-retention policy](https://bankstatement.ai/data-retention) describes the result, export, job-metadata, and upload lifecycles. Record an expiry deadline in the local manifest when each required result or export is created. Do not close the batch until the required outputs have been moved into the organization’s authorized storage or accounting workflow and the transfer has been confirmed. A controlled closeout sequence is: 1. Record the creation time and expiry deadline for every required result or export. 2. Download approved JSON, separate exports, or the combined XLSX promptly. 3. Transfer the files or data only to an authorized destination. 4. Confirm that the destination copy is readable and associated with the correct source records. 5. Mark the handoff complete in the manifest. ## FAQ ### How do you handle one failed statement in a batch? Keep the failed job associated with its local source identifier and move that source into quarantine. Preserve successful jobs and continue reviewing their results. Record any correction or retry as a separate, traceable attempt instead of restarting the entire batch. ### When should results be combined? Combine results only after every selected result has completed processing and passed human review for the intended handoff. Send only approved result IDs to the combined-XLSX endpoint. Keep exports separate when source-level handling and traceability matter more than operator convenience. ### What happens when page capacity or rate limits interrupt the run? HTTP 402 pauses affected new work until an authorized owner obtains adequate current page capacity or reduces the run’s scope. HTTP 429 allows the affected request to be retried after the supplied `retryAfterSeconds` interval. Neither response should erase the manifest state of jobs already created, completed, or under review. Review the current API and retention documentation first; if the workflow is appropriate, create an account and obtain an API token for server-side use.