Skip to main content
Process a real invoice dataset from Hugging Face using AsyncReducto with progress tracking and error handling.

Sample Dataset

We’ll use the Northwind Invoices dataset from Hugging Face, which contains 831 PDF invoices. Each invoice includes customer information, line items, and totals.
Sample Northwind invoice showing customer details, line items, and totals

Create API Key

1

Open Studio

Go to studio.reducto.ai and sign in. From the home page, click API Keys in the left sidebar.
Studio home page with API Keys in sidebar
2

View API Keys

The API Keys page shows your existing keys. Click + Create new API key in the top right corner.
API Keys page with Create button
3

Configure Key

In the modal, enter a name for your key and set an expiration policy (or select “Never” for no expiration). Click Create.
New API Key modal with name and expiration fields
4

Copy Your Key

Copy your new API key and store it securely. You won’t be able to see it again after closing this dialog.
Copy API key dialog
Set the key as an environment variable:

Download the Dataset

First, download the Northwind invoices dataset using the Hugging Face libraries:
You now have 831 PDF invoices ready to process.

Process the Batch

Document processing is network-bound, not CPU-bound. While your code waits for one API response, it could be uploading and processing other documents. Python uses AsyncReducto with asyncio, while JavaScript uses Promise.all() with the p-limit package for concurrency control.
Output:

Extract Structured Data

To extract specific fields like invoice numbers, totals, and line items, use the Extract API with a schema:
Output:

Cost Optimization with Job Chaining

Parse once, extract multiple times. When you need different extractions from the same documents, reuse the parse job ID to avoid re-parsing:
The jobid:// prefix tells Reducto to reuse an existing parse result. You only pay for parsing once, regardless of how many extractions you run.

Handling Failures

Add retry logic with exponential backoff to handle transient network errors:

Monitoring Job Status

When using webhooks or async jobs, you can poll for job status:
For production workloads, prefer webhooks over polling. Webhooks are more efficient and don’t require keeping connections open.

Why Concurrency Control?

The semaphore (asyncio.Semaphore in Python, p-limit in JavaScript) limits how many requests run simultaneously. Without it, submitting 831 files would create 831 concurrent connections, overwhelming both your system and the API. The concurrency limiter acts as a queue, letting only max_concurrency requests proceed at once. Start conservative and increase if stable. Larger files consume more memory per request, so lower concurrency prevents memory issues.

Fire-and-Forget with Webhooks

For very large batches where you don’t want to wait for results, use webhooks. Submit all jobs immediately and receive results as they complete via HTTP callbacks.
Your webhook receives a payload when each job completes:
Fetch the result in your handler with client.job.get(job_id).

Sync Alternative (Python)

If you can’t use async in Python, use threading with the synchronous Reducto client.
This section is Python-specific. The JavaScript SDK is async by default—all methods return Promises and work naturally with async/await and Promise.all(). No threading is needed in JavaScript.
Threading works but is less efficient than async for I/O-bound work. Use lower concurrency (10-20 workers) to avoid thread overhead.

Next Steps

Async Processing

Deep dive into async jobs and job lifecycle

Webhooks

Production webhook setup with Svix

Concurrency Throttle

Understand per-account concurrency limits for batch sizing

Job Management

Monitor and manage async jobs