An order export makes a tempting work queue: take the first 2,000 rows, click download, and repeat. That works only if the limit counts orders and each row has the same relationship to a document. Neither assumption belongs in a billing collector without evidence.
I'm writing from BrowserAct. While adapting our Amazon Business invoice guide, the useful engineering problem was the unit of work: how should an application plan bounded document retrieval when one purchase can have several accounting artifacts? The implementation below is an application-side design with synthetic checks. It does not log into Amazon, call an invoice API, or demonstrate a working portal collector.
Establish the unit before the batch size
Amazon Business's reconciliation guide distinguishes Business Analytics order-document downloads from the eligible Your invoices workflow. The guide documents up to 2,000 documents per analytics download. For the invoice-account workflow, it describes invoices and credit memos packaged into separate ZIP files for each 2,000 documents. Those are related capacity facts with different collection routes; they are not permission to assume identical controls in every marketplace.
A report row, an expected accounting document, and a returned file should therefore have separate identities. An order could require an invoice and a credit memo. Another order may need an individually retrieved seller invoice. A filtered report can contain both, while the native bulk route covers only part of the required set.
Start with a reviewed expectation list. If you only know the order count, retain an unresolved planning task until document expectations are established. Do not create one imaginary invoice per row merely to make the arithmetic convenient.
Keep scope in the partition key
Use the authorized account or group, marketplace, and collection route as a partition key. An administrator's broad visibility does not authorize silently combining another group's documents. The route also matters: Amazon's guide excludes third-party seller invoices from the described bulk-invoice download, so seller follow-up remains a separate queue.
Here is a deliberately small expectation record. These identifiers are invented; an implementation must define stable, scoped document identities from its own approved source.
{
"id": "uk-finance:invoice:sample-17",
"order": "sample-order-9",
"account": "uk-finance",
"market": "UK",
"route": "analytics",
"kind": "vat_invoice"
}
Document type remains explicit. A credit memo should not become a second payable invoice, and a US printable order summary should not be renamed an EU VAT invoice. The planner does not determine tax validity; it preserves the classification supplied by the reviewed expectation list.
Build a deterministic application plan
The function counts expected documents, sorts each qualified scope by stable ID, and slices it at a configured capacity. It refuses duplicate IDs and unknown routes. It makes no claim that a portal lets an operator select an arbitrary list of IDs; an adapter must translate each batch into available filters and verify the resulting scope before submitting a download.
from collections import defaultdict
from dataclasses import dataclass
@dataclass(frozen=True)
class Document:
id: str
order: str
account: str
market: str
route: str # analytics, credit_account, or seller
kind: str
def plan(documents, capacity=2000):
if type(capacity) is not int or capacity < 1:
raise ValueError("capacity must be a positive integer")
groups, seller, seen = defaultdict(list), [], set()
for doc in documents:
if not all((doc.id, doc.order, doc.account, doc.market, doc.kind)):
raise ValueError("incomplete expected document")
if doc.id in seen:
raise ValueError("duplicate expected document ID")
seen.add(doc.id)
if doc.route == "seller":
seller.append(doc)
elif doc.route in {"analytics", "credit_account"}:
groups[(doc.account, doc.market, doc.route)].append(doc)
else:
raise ValueError("unqualified collection route")
batches = []
for scope, items in sorted(groups.items()):
items.sort(key=lambda doc: doc.id)
for offset in range(0, len(items), capacity):
batch = items[offset:offset + capacity]
batches.append({
"scope": scope,
"expected_ids": [doc.id for doc in batch],
"document_count": len(batch),
})
return {"batches": batches, "seller_follow_up": seller}
Sorting makes the plan reproducible for an unchanged snapshot. Persist that snapshot and its plan together before executing any download. If the expected set changes, create a new plan version instead of changing a running batch's membership. A batch number alone is a poor retry key because inserting an earlier document can move every later boundary.
The return value also keeps seller follow-up visible. It does not mark those documents missing from a successful native ZIP as a bulk-download failure. They have a different retrieval responsibility from the start.
Check the boundary with synthetic inputs
Use a smaller capacity in development so failures are readable, then exercise the actual configured boundary. I ran the following synthetic checks against this function; none represents an Amazon account download.
| Synthetic case | Required result |
|---|---|
| 1,999, 2,000, and 2,001 documents in one scope | Batch sizes [1999], [2000], and [2000, 1]
|
| One order with three expected documents, capacity two | Two batches; the order ID is not the counting unit |
| The same expected document ID appears twice | Reject the plan instead of scheduling it twice |
| Two authorized accounts each have one document | Separate batches even with spare capacity |
| One native document and one seller invoice | One native batch and one seller follow-up item |
| Unknown route or nonpositive capacity | Reject before creating executable work |
These checks prove only the local partitioner's behavior. They do not prove completeness of the expectation list, permission in a live account, or availability of the original files. The 2,000 default should remain a reviewed route configuration, not a universal constant for every vendor.
Hand off a bounded request, then prove delivery
When an adapter cannot express a planned batch with the portal's selectors, stop and refine the plan. Selecting a whole date range that exceeds the known document bound is not an implementation of the requested batch. Likewise, the invoice-account workflow may return several ZIPs; archive count and document count are separate fields.
After retrieval, compare each returned document with its expected identity and type. Preserve unresolved records and seller follow-up until their actual artifacts are obtained. Payment reconciliation has another key: Amazon identifies Charge Identifier for card payments and Invoice ID or PO number for BCA payments. Keep those business identifiers alongside the document manifest rather than treating a filename or order count as proof of financial matching.
The source guide also flags a regional API boundary: the reviewed getInvoices reference listed JP for its region parameter. That does not establish a UK/EU API replacement. This planner intentionally contains no API transport layer.
For the native paths, permission boundaries, and source context, see our Amazon Business invoice guide. BrowserAct could be evaluated for authorized navigation or ZIP handoff around native retrieval, but this example establishes no validated Amazon Business Bot. The acceptance question stays concrete: can the collector deliver every expected artifact for the authorized scope, while keeping excluded seller work visible?
Top comments (0)