The multi-entity problem, not the single-account problem
A single company checking its own bank balance is a manageable problem — one account, one currency, one number to trust. A treasury or cash-visibility platform's actual job is a different shape entirely: a corporate group with dozens or hundreds of legal entities, each with its own accounts, often in its own local currency, all needing to roll up into one coherent picture a treasurer can act on the same morning. Multi-entity cash position extraction is the feature this page is about — turning statements from across that whole structure into a consistent, structured, currency-tagged shape, independent of how many entities or currencies the group actually spans.
This is a meaningfully different engineering problem than extracting one statement well. Accuracy on a single document is a prerequisite, not the whole story — the harder part, and the part this feature is built around, is doing that extraction consistently across a genuinely global mix of banks, currencies and statement conventions, without the pipeline degrading or needing per-country tuning as the group's footprint grows.
What multi-entity extraction means here
There is no separate "group mode" or dedicated multi-entity endpoint — multi-entity extraction is the natural result of calling POST /extractmany times concurrently across a group's statements, exactly the way the treasury-platform API is used for a single client. What makes it "multi-entity" is entirely on your side — how your pipeline tags each call with the entity it belongs to and assembles the results afterward.
This design choice is deliberate rather than a missing feature. A treasury platform's entity hierarchy — which subsidiary rolls up into which regional holding company, which accounts belong to which cost center — is exactly the kind of business logic that shouldn't live inside a document-extraction API. Keeping the API entity-agnostic means your platform's own data model stays the single source of truth for structure, while the API stays focused on the one thing it does well: turning a statement into correct, validated data.
The shape of a multi-entity batch
A typical group-wide refresh pulls every entity's pending fallback statements — each already tagged with an entity ID, account ID and expected currency on your side — and issues one /extract call per statement, correlating each response back to the entity it belongs to.
const queue = await getPendingStatements()
// [{ entityId, accountId, expectedCurrency, fileUrl }, ...]
async function processOne(stmt) {
const result = await extract(stmt.fileUrl)
await saveResult(stmt.entityId, stmt.accountId, result)
}
// Concurrency-limited refresh across the whole group
await runWithConcurrency(queue, processOne, { concurrency: 25 })How currency is handled — read, not converted
Every statement returns whichever currency is printed on it — the account's own operating currency, not a figure translated into anything else. A Mexican peso account and a Swiss franc account in the same batch each come back tagged with their own currency code, exactly as an analyst reading the statement directly would record it.
This is a deliberate boundary, not a limitation waiting to be added later. Currency conversion depends on a rate source, a rate date convention and often a policy decision (spot rate, period- average, a locked internal rate) that varies by treasury team and even by reporting purpose within the same team — decisions that belong in your platform's own consolidation layer, not baked into an extraction API that has no visibility into which convention a given client uses.
Tagging results back to entity, account and currency
The API has no concept of entity, subsidiary or group — it receives a statement and returns a result, nothing more. Correlating that result with the right entity is your pipeline's responsibility, the same discipline it already applies to keeping a live feed's data organized by entity today. The pattern in the code sample above — tagging each queue item before the call, writing the result under that same tag afterward — is how essentially every production integration handles this.
Per-account balance checks across the whole group
Every statement in a group-wide batch is checked against its own closing balance independently of every other statement — an entity in Poland reconciling correctly has no bearing on whether an entity in Chile does. This per-statement validation is what lets a treasury team trust a full group refresh at face value, rather than spot-checking arithmetic across dozens of accounts and currencies by hand after the fact.
This independence matters specifically at group scale, where it's tempting to treat one flagged account as a signal to distrust the whole refresh. It isn't — a flagged statement is a property of that one account, not a reason to re-check the other 24 entities in the same batch. Treating each flag in isolation is what keeps a treasury team's review time proportional to genuine exceptions rather than to group size.
Where consolidation actually happens
This feature deliberately stops at the account level — a validated balance and transaction list per statement, tagged with its own entity and currency. Rolling those individual results up into a single group-wide position, in whatever reporting currency and at whatever consolidation logic a treasury desk uses, is your platform's job, not the extraction API's.
That boundary is worth being explicit about, because it's the single most common point of confusion for a team integrating this for the first time: the API gives you clean, comparable inputs to a consolidation step your platform already has (or is building) for live-feed accounts — it doesn't replace that consolidation logic, and shouldn't need to, since a fallback account's validated data is structurally identical to a live-feed account's once it's returned.
Retry strategy for a group-wide refresh
Because every call is independent, a failed request in the middle of a 25-entity refresh is a single retry, not a reason to restart the run. Most production integrations wrap each call in a small exponential-backoff retry before routing a still-failing statement to manual handling — the same pattern any team already uses for any external API call, unchanged by how many entities or currencies a given refresh spans.
What building this yourself involves
None of the batch mechanics here are exotic — a worker pool, an entity-tagging layer and a retry wrapper are standard patterns most engineering teams already know how to build. What a platform is not building, by using the extraction API underneath this pipeline, is the document-understanding model itself: reliably classifying and extracting fields across a genuinely global range of bank layouts and currencies, plus the arithmetic validation, is months of work and a labeled dataset most treasury platforms don't have sitting around.
| Component | Effort if built in-house |
|---|---|
| Worker pool + queue + retry logic | A few days, standard engineering patterns |
| Entity/account tagging and correlation layer | A few days — reuses your existing hierarchy |
| Document classification + field extraction across banks and currencies | Months, plus a globally representative labeled dataset |
| Arithmetic/balance validation logic | Weeks, with ongoing tuning as unfamiliar layouts surface |
Steps to run your first multi-entity batch
Get an API key
A free plan account gets full accuracy against a smaller monthly allowance — enough to test a real multi-entity batch.
Build a small queue and worker pool
Most teams start with a concurrency of 10–25 and adjust based on how quickly a group refresh needs to clear.
Tag each statement with entity, account and expected currency
This is the correlation key your pipeline uses to file the result correctly afterward.
Run a real pilot batch across a handful of entities
A batch of statements from three to five entities in different currencies is enough to validate the whole pipeline end to end.
Add retry handling and route flagged results to review
Standard exponential-backoff retry on failures, with low-confidence or unreconciled results routed separately.
A worked example: a 25-entity group refresh
A corporate group with 25 entities across nine currencies needs its weekly fallback accounts refreshed — 60 statements in total, averaging 3 pages each. At a concurrency of 25 in-flight requests and roughly 4 seconds per document including network round-trip, the full refresh completes in:
| Concurrency | Approx. total time, 60 statements |
|---|---|
| 1 (sequential) | ~4 minutes |
| 10 | ~30 seconds |
| 25 | Under 15 seconds |
At 180 pages total, the extraction cost for this refresh is €6.30 — well inside the noise for a platform whose value proposition is a genuinely complete, group-wide position, not a partial one missing whichever accounts happen to lack a live connection.
Confidence scoring across mixed-quality statements
A real group-wide refresh is never uniform quality — a clean digital PDF from a well-connected subsidiary next to a slightly blurry scan from a smaller foreign bank with no online-banking export option at all. Each statement gets its own independent confidence signal, so a low-confidence statement from one entity doesn't drag down or get averaged with a high-confidence one from another entity in the same batch.
Handling statements that don't reconcile
A statement whose transactions don't sum to its closing balance is flagged in its own response rather than silently folded into the group position — your pipeline can route these specifically to a treasury analyst, separate from the low-confidence-but-valid statements that just need a quick visual check.
Throughput at group-wide scale
Most integrations settle on a concurrency between 10 and 50 in-flight requests, which comfortably clears even a large multinational group's weekly fallback refresh inside a few minutes. Platforms serving many clients, each with their own group structure, typically raise concurrency gradually as overall platform-wide volume grows, monitoring error rates rather than jumping straight to a very high setting.
Where FX conversion belongs, and where it doesn't
Because the API returns each statement's own currency unconverted, your platform's existing FX-conversion logic — whatever rate source and convention it already applies to live-feed accounts — applies identically to fallback accounts. There is no second conversion pathway to build or maintain specifically for extracted data; a fallback account's currency-tagged result enters your consolidation step exactly the way a live-feed account's does.
This matters for auditability as much as for engineering simplicity: a treasurer asking why a consolidated figure looks a certain way gets one consistent answer about which rate and date convention was applied, regardless of whether the underlying account came from a live feed or a PDF fallback.
Testing a multi-entity batch before it touches production
Before a group-wide refresh writes into a client-facing position, it's worth running it once against a small, representative slice of the group rather than the whole structure at once — three or four entities spanning different currencies and at least one unfamiliar bank is usually enough to surface a tagging bug, a currency mismatch, or a statement layout the pipeline handles differently than expected, before it happens across a hundred accounts simultaneously.
A useful discipline here is picking entities you can independently verify — an account whose current balance a treasury analyst already knows offhand, or one with a recent manual entry to compare against. Confirming the API's output matches a number you can already trust is a far more convincing test than checking that the pipeline runs without throwing an error, since a pipeline can run cleanly end to end and still write a subtly wrong currency tag or a misattributed entity if the correlation logic has a bug.
Once a small representative batch has been checked this way, scaling the same pipeline to the full group is mechanical — nothing about extraction quality or entity tagging changes with scale, only the number of statements queued. Teams that skip this small-batch check and go straight to a full group refresh tend to find their first real bug in front of a client rather than in a controlled test, which is the specific outcome this step is meant to avoid.
Edge cases a multi-entity pipeline needs to expect
| Case | How to handle it |
|---|---|
| A statement's currency doesn't match what your system expected | Trust the printed currency the API returns and flag the mismatch for review — the account may have been miscategorized on your side |
| An entity has accounts at more than one bank | Tag each statement independently — nothing requires a one-entity-to-one-bank assumption |
| A single call times out or errors | Retry with backoff — the rest of the group refresh is unaffected |
| A very large multi-page annual statement | Processed the same as any document — pages billed at the same flat per-page rate |
Who this is built for
Treasury platforms serving multinational groups
Where dozens of entities and currencies are the normal case, not the exception.
Cash-pooling and consolidation tools
Needing consistent, comparable account-level data before consolidation logic runs.
Engineering teams building an internal treasury ops pipeline
Assembling their own queue and worker pool around a reliable extraction API.
Platforms migrating off manual multi-currency spreadsheet rollups
Where a person currently keys foreign statements into a consolidation workbook by hand.
Keeping an audit trail across a group
Because every response is returned directly to your call rather than retained centrally, the audit trail for a group-wide refresh lives entirely in your own system — each result written alongside its source statement reference and a timestamp the moment it comes back, giving a treasury team a complete, reviewable record of exactly what was extracted from which entity's account, without depending on FlowParse to retain anything after the call completes.
Where this fits in an existing treasury workflow
Multi-entity extraction typically sits right after fallback-statement collection and right before consolidation — statements land in an intake queue from wherever a treasury team already gathers them, a scheduled or manually-triggered refresh processes everything pending, and the extracted, validated, currency-tagged results feed straight into whatever consolidation and reporting logic already exists for live-feed accounts. Nothing about adopting this changes the surrounding workflow; it fills in the accounts a live feed can't reach.
For a platform building this fresh, that placement — after collection, before consolidation — is worth designing in deliberately rather than discovering later, since it determines how cleanly the fallback path can be added without disturbing the live-feed path already running in production. The cleanest integrations treat the extraction API as one of several interchangeable data sources feeding the same consolidation step, distinguished only by a source tag on each result, rather than as a separate system requiring its own reporting logic downstream.
What this feature does not do
It does not convert currencies, decide a group's consolidation hierarchy, or replace a treasurer's judgment about an unusual intercompany transfer — it turns a statement into structured, validated, currency-tagged data, which is the mechanical step immediately before consolidation, not a replacement for the consolidation logic or the judgment that follows it.
Security and data handling
Uploads are encrypted with TLS from end to end.
Processing runs on infrastructure with SOC 2-aligned controls.
Original statements 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 entity statements, and run your first concurrent multi-entity batch. A free plan account processes real documents at full accuracy against a smaller monthly allowance.
