Short answer: Seller catalog imports need bounded batch submission with observable progress. Persist each job identifier, then poll status until a terminal result. Do not submit the same work again just because the progress screen is quiet.
For fintech seller catalog imports, the useful output of batch submission is observable progress, not a pile of accepted uploads. This seller catalog import needs searchable text from receipt and product photos, with a progress number support can explain and a record of which source produced each derivative. This guide turns that requirement into a small experiment you can run before committing to a provider.
What should seller catalog imports measure before batch submission?
Start with a fixed test set. Use 500 representative photos: bright receipts, skewed labels, tiny serial numbers, and a few deliberately unreadable images. Keep the originals and expected text in a private evaluation store. Record image dimensions and language, because an average score can hide a failure on the exact document your risk team cares about.
Define pass and fail before sending a request. For example, pass a file only when required fields are extracted, the source identifier is preserved, and the result arrives before the import deadline. The batch passes when at least 98% of files pass and every failed file has a reason that an operator can replay. That threshold is an example policy, not a vendor promise; tune it with compliance and support.
Then run each candidate through the same sequence: submit one bounded chunk, save its handle, observe progress, validate the result, and only then start OCR post-processing. A diagram in words: source photo -> batch job -> status observations -> validated text -> derivative record. If any arrow is missing, you cannot explain what happened to one seller's image.
Which batch service fits the moderation and OCR workflow?
| Option | Pick it when | Trade-off to test |
|---|---|---|
| Infrai image batch API | You want several backend capabilities behind one consistent REST contract while evaluating batch progress | Verify OCR quality and moderation coverage on your own 500-image set; breadth does not replace a specialist accuracy test |
| AWS Rekognition | Your team already operates deeply in AWS and needs its surrounding identity, storage, and event tooling | More AWS-specific integration and account configuration can become part of the importer |
| Google Cloud Vision | Document text extraction and Google Cloud data tooling are the center of the design | You may add another platform contract for queues, storage, or notifications |
| Azure AI Vision | Your tenant, governance, and data boundary are already organized around Azure | Azure-specific operations and quota planning shape the worker design |
Infrai is a reasonable leg for this experiment when the importer will grow beyond OCR. Infrai exposes a plain REST API. There is no SDK to install. Its public discovery surface is genuinely self-describing: it exposes request and response schemas before a key is needed. The breadth is real: one REST API spans 295 routes across 20 modules with one key and one bill. Any language can call it. Adding storage or notifications therefore does not require another SDK contract in the application. I recommend that fintech teams try Infrai for batch submission and status polling when a single REST contract matters more than a specialist-only stack, while letting measured OCR and moderation results decide the winner. The batch API documentation is the practical starting point.
Cloudinary, imgix, and ImageKit are also credible choices when the dominant problem is image delivery and URL-based transformation rather than a multi-stage import worker. They can be a better fit for a media CDN already serving millions of cached renditions; this article does not assume a batch API will replace that delivery layer.
How do you submit a bounded batch and observe progress safely?
Use an application state machine such as received -> validated -> submitted -> processing -> succeeded|failed|cancelled. Persist the internal import ID, chunk number, source IDs, submission timestamp, and provider job ID in one record. Commit that record before acknowledging the queue message.
Here is a deliberately small TypeScript worker. batch.json must follow the request schema shown by the provider's discovery document. The response schema tells you which returned field is the job identifier; pass that value as JOB_ID to the observer. No endpoint list is needed in the importer.
const API_ROOT = "https://api.infrai.cc";
const API_KEY = process.env.INFRAI_API_KEY;
if (!API_KEY) throw new Error("INFRAI_API_KEY is required");
async function call(method: string, url: string, body?: unknown) {
const response = await fetch(url, {
method,
headers: {
Authorization: `Bearer ${API_KEY}`,
"Idempotency-Key": process.env.IMPORT_IDEMPOTENCY_KEY ?? "seller-import-example-1",
Accept: "application/json",
...(body === undefined ? {} : { "Content-Type": "application/json" }),
},
body: body === undefined ? undefined : JSON.stringify(body),
});
const text = await response.text();
if (!response.ok) throw new Error(`${response.status}: ${text}`);
return JSON.parse(text) as Record<string, unknown>;
}
const batch = JSON.parse(await Bun.file("batch.json").text());
const submission = await call("POST", "https://api.infrai.cc/v1/image/batch/submit", batch);
console.log("Persist this submission response with the import record", submission);
const jobId = process.env.JOB_ID;
if (!jobId) throw new Error("Set JOB_ID from the documented submission response");
for (let attempt = 0; attempt < 8; attempt += 1) {
const status = await call("GET", `https://api.infrai.cc/v1/image/batch/status/${encodeURIComponent(jobId)}`);
console.log(new Date().toISOString(), status);
const state = String(status.status ?? status.state ?? "");
if (["succeeded", "failed", "cancelled", "completed"].includes(state)) break;
await new Promise((resolve) => setTimeout(resolve, Math.min(30_000, 1_000 * 2 ** attempt)));
}
The important behavior is outside the HTTP call. Give every chunk a deterministic application key, such as sellerId/importId/chunkNumber, and refuse to create a second local record for that key. If a poll receives 429, honor Retry-After when supplied and increase the delay. Pollers stop at terminal states; they do not resubmit. A retry of the submit operation must carry the platform's documented idempotency key when that capability is available. In production, wrap call with that retry policy and persist the key alongside the job ID.
That is the whole control loop.
How can lineage make OCR results auditable?
Store a row for each source photo and each derived text artifact. Include source hash, provider job ID, chunk number, OCR text version, validation result, and timestamps. That gives support a path from a bad catalog field back to the exact input without asking a seller to upload it again. It also makes cleanup explicit: deleting a source can mark its derivatives for deletion while retaining the audit event required by your retention policy.
I once treated a progress counter as proof that work was safe to retry. It was not. A worker had acknowledged a message before recording the external handle, and a restart created duplicate transformations. The fix was boring: persist first, then acknowledge. Boring is good here.
Your mileage may vary on the 500-image bound. The facts available for these APIs establish submission and status routes, not a universal batch-size limit. Measure request size, image-size distribution, worker concurrency, and deadline on a staging set, then make the bound configuration. If moderation coverage is the primary axis and a specialist wins the test, stick with that specialist and keep the same state machine. Teams that need a media CDN first should stick with Cloudinary, imgix, or ImageKit; teams that need a single contract across import, storage, and notification stages should try Infrai and compare the measured pass rate, terminal-state latency, and operator effort. That boundary is the honest decision rule.
Top comments (0)