FlowParse
Feature September 2026 16 min read

Multi-client batch document extraction

Process statements and invoices for many clients at once through concurrent calls to one extraction API — no per-client setup, confidence-scored results ready for review or straight-through import.

FlowParse
flowparse.io

The batch problem, not the single-document problem

A tool built for one document at a time — upload a statement, wait, review, download — works fine for an individual filing a return once a year. It breaks down the moment the actual workload is several hundred documents from dozens of different clients arriving in the same few days around a monthly close. Multi-client batch extraction is the feature this page is about: sending many documents through the same API concurrently, with each one classified, extracted and validated independently, so the total time to process a close batch is bounded by concurrency, not by document count processed one after another.

This is the specific gap between a tool built to show one person their own numbers and infrastructure built to process a firm's entire operation. A consumer-shaped converter optimizes for the experience of a single upload — a progress bar, a preview, a download button — and none of that UX scales to hundreds of documents landing at once from dozens of unrelated clients. The feature this page describes isn't a bigger version of that single-document experience; it's a different shape entirely, built around a pipeline rather than a page.

FlowParse
flowparse.io

What multi-client batch extraction means here

There is no separate "batch mode" toggle or a distinct endpoint — batch processing is the natural result of calling POST /extractmany times concurrently rather than once. Each call is completely independent: its own document, its own classification, its own validation, its own response. What makes it "batch" is entirely on your side — how many of those calls your pipeline fires in parallel and how it correlates the responses back to the right client and engagement.

This design choice — no server-side batch job, no job-status endpoint to poll, just independent request/response pairs — is deliberate rather than a missing feature. A job-queue API adds its own complexity: a job ID to track, a polling loop to write, a separate failure mode for "the job itself failed" versus "one document in it failed." Independent calls sidestep all of that; the only state that needs to exist is whatever your own pipeline already tracks about which documents are pending, in flight, and done.

The shape of a batch request

A typical batch pipeline pulls a list of pending documents — each already tagged with a client ID and document ID on your side — and issues one /extract call per document, tracking which response belongs to which source document by request order or a client-supplied reference passed through your own queue.

batch worker, simplified
const queue = await getPendingDocuments() // [{ clientId, docId, fileUrl }, ...]

async function processOne(doc) {
  const result = await extract(doc.fileUrl)
  await saveResult(doc.clientId, doc.docId, result)
}

// Concurrency-limited batch — e.g. 20 in flight at once
await runWithConcurrency(queue, processOne, { concurrency: 20 })

How concurrency actually works

Because every call is independent and stateless, there's no coordination needed between concurrent requests — document 40 doesn't wait on document 39 to finish. Most integrations use a simple worker-pool pattern, capping the number of in-flight requests at a fixed number rather than firing the entire batch at once, which keeps the pipeline predictable and easy to reason about under load.

FlowParse
flowparse.io

Matching results back to the right client and document

The API has no concept of a client — it receives a document and returns a result, nothing more. Correlating that result with the right client and engagement is your pipeline's responsibility, the same way it's already responsible for keeping client files organized before anything is sent for extraction. The pattern above — tagging each queue item with a client ID before the call, then writing the result under that same ID afterward — is how essentially every production integration handles this.

FlowParse
flowparse.io

Per-client balance and total checks

Every statement in a batch is checked against its own closing balance, and every invoice against its own printed subtotal, independent of every other document in the batch — client 12's statement reconciling correctly has no bearing on whether client 45's does. This per-document validation is what lets a review team trust a batch result at face value rather than manually re-checking arithmetic across hundreds of documents after the fact.

FlowParse
flowparse.io

This independence is worth dwelling on because it's easy to assume, incorrectly, that a validation error somewhere in a large batch means something is wrong with the batch as a whole. It doesn't — a flagged statement is a property of that one document, not a signal to distrust the other 299 in the same run. Treating each flag as isolated, rather than triggering a full-batch re-check, is what keeps a review team's time proportional to the number of genuine exceptions rather than the size of the batch.

Retry strategy for a large batch

Because every call is independent, a failed request in the middle of a 300-document batch is a single retry, not a reason to restart the run. Most production integrations wrap each call in a small exponential-backoff retry — attempt, wait briefly, attempt again, wait longer, attempt a final time — before routing a still-failing document to manual handling. This is the same pattern any team already uses for any external API call; nothing about batch volume changes the approach, only how many times it's applied in a single run.

A useful discipline is separating two different failure types in your own logging: a transient network or timeout failure, which usually succeeds on retry, versus a document the API processed but flagged as low-confidence or unreconciled, which is a data-quality signal, not an error to retry away. Conflating the two in a single "failed" bucket makes it harder to see which problem a rising failure rate is actually pointing at.

What building a queue like this yourself involves

None of the batch mechanics described here are exotic — a worker pool, a queue, a retry wrapper are standard patterns most engineering teams already know how to build. What a firm is not building, by using the extraction API underneath this queue, is the actual document-reading model: the OCR, the classification, the field extraction and the arithmetic validation that would otherwise need months of its own development before the queue around it even matters.

It's worth being specific about where the real engineering effort in a project like this actually goes, because it isn't evenly distributed. A worker pool and a retry wrapper are a few days of work for a competent backend engineer, reusable from any past project involving an external API. The document-understanding layer underneath — reliably classifying a statement versus an invoice, extracting fields across hundreds of real bank and vendor layouts, and checking the arithmetic before returning a result — is the part that takes months and a labeled dataset most firms don't have sitting around. Batch extraction lets a firm build the small, fast part in-house and buy the slow, hard part as an API call.

ComponentEffort if built in-house
Worker pool + queue + retry logicA few days, standard engineering patterns
Client-tagging and correlation layerA few days — reuses existing internal IDs
Document classification + field extractionMonths, plus a labeled training set
Arithmetic/balance validation logicWeeks, and ongoing tuning as edge cases surface

Steps to run your first batch

1

Get an API key

A free plan account gets full accuracy against a smaller monthly allowance — enough to test a real batch.

2

Build a small queue and worker pool

Most teams start with a concurrency of 10–20 and adjust based on how quickly their intake volume needs to clear.

3

Tag each item with a client ID before sending

This is the correlation key your pipeline uses to file the result under the right client afterward.

4

Run a real pilot batch

A batch of 20–50 real documents across a handful of clients is enough to validate the whole pipeline end to end.

5

Add retry handling for failed calls

Standard exponential-backoff retry on network failures, same as any external API call in production.

A worked example: queuing a 300-document monthly close

A firm with 60 clients collects roughly 300 documents (statements, invoices and receipts combined) for a given monthly close. At a concurrency of 20 in-flight requests and an average of 4 seconds per document (including network round-trip), the full batch completes in roughly:

ConcurrencyApprox. total time, 300 documents
1 (sequential)~20 minutes
10~2 minutes
20~1 minute

Even a modest concurrency setting turns a batch that would take twenty minutes sequentially into a job that finishes before a reviewer has opened their inbox — the full close-batch numbers at larger client counts are in the document extraction API for bookkeeping firms page.

FlowParse
flowparse.io

Confidence scoring across a mixed batch

A real intake batch is never uniform quality — a clean digital PDF statement next to a slightly blurry phone photo of a receipt. Each document gets its own independent confidence signal reflecting its own extraction quality, so a low-confidence receipt doesn't drag down or get averaged with a high-confidence statement sitting next to it in the same batch.

FlowParse
flowparse.io

Handling documents that don't reconcile

A statement whose transactions don't sum to its closing balance, or an invoice whose line items don't match its subtotal, is flagged in its own response rather than silently passed through — your pipeline can route these specifically to a reviewer, separate from the low-confidence-but-valid documents that just need a visual double-check.

FlowParse
flowparse.io

Throughput limits and how to plan around them

Most integrations settle on a concurrency between 10 and 50 in-flight requests, which comfortably clears a several-hundred-document monthly batch inside a coffee break. Firms processing multiple thousands of pages in a single run typically raise concurrency gradually while monitoring error rates, rather than jumping straight to a very high number — standard practice for any external API integration at scale.

Mixed document types in the same batch

Because document type is classified automatically per call, a batch doesn't need pre-sorting into "statements" and "invoices" piles before it's sent — a real client intake folder, with whatever mix actually arrived, can be queued as-is.

FlowParse
flowparse.io

Edge cases a batch pipeline needs to expect

CaseHow to handle it
A single call times out or errorsRetry with backoff — the rest of the batch is unaffected
A document is genuinely unreadableReturned as low-confidence, routed to manual review
Duplicate document uploaded twiceDeduplicate on your side before sending — the API has no cross-request memory to detect this itself
A very large multi-page statementProcessed the same as any document — pages billed at the same flat per-page rate

Who this is built for

Bookkeeping BPOs running a monthly or weekly close

Needing hundreds of client documents processed inside a tight close window.

Practice-management platforms adding bulk import

Wanting to offer clients a batch-upload feature backed by real extraction.

Engineering teams building an internal ops pipeline

Assembling their own queue and worker pool around a reliable extraction API.

Firms migrating off a slower, one-at-a-time tool

Where sequential processing has become the bottleneck as volume grows.

Keeping an audit trail across a batch

Because every response is returned directly to your call rather than retained centrally, the audit trail for a batch lives entirely in your own system — typically, each result is written alongside the original document reference and a timestamp the moment it comes back, giving a firm a complete, reviewable record of exactly what was extracted from which document for which client, without depending on FlowParse to retain anything after the call completes.

FlowParse
flowparse.io

Where this fits in an existing close workflow

Batch extraction typically sits right after document collection and right before review — documents land in an intake folder or upload portal from clients throughout the month, a scheduled or manually-triggered batch run processes everything pending, and the extracted, validated results feed straight into whatever review step and GL import already exists downstream. Nothing about adopting this changes the surrounding workflow; it replaces the manual-entry step in the middle of it.

What batch extraction does not do

It does not categorize transactions against a client's specific chart of accounts, decide whether a large deposit needs a client conversation, or replace the accounting judgment a bookkeeper applies after the data is in front of them — it turns a document into structured, validated data, which is the mechanical step immediately before that judgment, not a replacement for it.

Security and data handling

Uploads are encrypted with TLS from end to end.

Processing runs on infrastructure with SOC 2-aligned controls.

Original documents are deleted shortly after processing.

Nothing uploaded is ever used to train AI models.

Full details are on the security page.

Get your API key

Build a small worker pool, tag a handful of real documents with client IDs, and run your first concurrent batch. A free plan account processes real documents at full accuracy against a smaller monthly allowance.

Frequently asked questions

Run your first batch in minutes

Get a free API key and process a real batch of client documents today.

Keep reading