Reducto offers two processing modes: synchronous and asynchronous. The SDK provides run() and run_job() methods that map to different API endpoints:
| SDK Method | API Endpoint | Returns |
|---|
client.parse.run() | POST /parse | Full result (blocks until complete) |
client.parse.run_job() | POST /parse_async | Job ID (returns immediately) |
The same pattern applies to all endpoints: /extract vs /extract_async, /split vs /split_async, and /pipeline vs /pipeline_async.
The Go SDK is currently in alpha and has limited async support. Go users should use the REST API directly for async operations. See the cURL examples below.
run() vs run_job()
| Method | Behavior | Best for |
|---|
run() | Calls sync endpoint, blocks until complete | Interactive applications, smaller documents |
run_job() | Calls async endpoint, returns job ID | Large documents, high volume, background processing |
Both methods produce the same results. The difference is whether you wait synchronously or retrieve results later.
Synchronous: run()
from reducto import Reducto
client = Reducto()
# Blocks until parsing completes (may take seconds to minutes)
result = client.parse.run(input="https://example.com/document.pdf")
print(result.result.chunks)
import Reducto from "reductoai";
const client = new Reducto();
// Blocks until parsing completes
const result = await client.parse.run({ input: "https://example.com/document.pdf" });
console.log(result.result.chunks);
package main
import (
"context"
"fmt"
"os"
reducto "github.com/reductoai/reducto-go-sdk"
"github.com/reductoai/reducto-go-sdk/option"
"github.com/reductoai/reducto-go-sdk/shared"
)
func main() {
client := reducto.NewClient(option.WithAPIKey(os.Getenv("REDUCTO_API_KEY")))
// Blocks until parsing completes
result, err := client.Parse.Run(context.Background(), reducto.ParseRunParams{
ParseConfig: reducto.ParseConfigParam{
DocumentURL: reducto.F[reducto.ParseConfigDocumentURLUnionParam](
shared.UnionString("https://example.com/document.pdf"),
),
},
})
if err != nil {
panic(err)
}
fmt.Println(result.Result.Chunks)
}
curl -X POST "https://platform.reducto.ai/parse" \
-H "Authorization: Bearer $REDUCTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input": "https://example.com/document.pdf"}'
The run() method handles the job lifecycle internally. If the document takes too long, the request may time out. For documents over 50 pages or complex processing, consider using run_job() instead.
Asynchronous: run_job()
from reducto import Reducto
client = Reducto()
# Returns immediately with job ID
submission = client.parse.run_job(input="https://example.com/document.pdf")
print(f"Job submitted: {submission.job_id}")
# Retrieve results later via polling or webhook
import Reducto from "reductoai";
const client = new Reducto();
// Returns immediately with job ID
const submission = await client.parse.runJob({ input: "https://example.com/document.pdf" });
console.log(`Job submitted: ${submission.job_id}`);
// Retrieve results later via polling or webhook
# Call the async endpoint directly
curl -X POST "https://platform.reducto.ai/parse_async" \
-H "Authorization: Bearer $REDUCTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input": "https://example.com/document.pdf"}'
# Response: {"job_id": "abc123-def456"}
The run_job() method has no limit on concurrent submissions. You can queue thousands of documents and process them in parallel without managing connections or timeouts.
Job lifecycle
When you submit a job via the async endpoint, it moves through these states:
| Status | Meaning |
|---|
Pending | Job is queued, waiting for a worker |
InProgress | A worker is actively processing the document |
Completing | Processing finished, results being saved |
Completed | Results are ready to retrieve |
Failed | Processing failed (check error message) |
Jobs typically spend most of their time in Pending (waiting for capacity) or InProgress (actual processing).
Polling for results
The simplest way to get results from an async job is to poll the job status:
import time
from reducto import Reducto
client = Reducto()
# Submit job
submission = client.parse.run_job(input="https://example.com/document.pdf")
# Poll until complete
while True:
job = client.job.get(submission.job_id)
if job.status == "Completed":
print("Success:", job.result)
break
elif job.status == "Failed":
print("Failed:", job.error)
break
time.sleep(2)
import Reducto from "reductoai";
const client = new Reducto();
// Submit job
const submission = await client.parse.runJob({ input: "https://example.com/document.pdf" });
// Poll until complete
while (true) {
const job = await client.job.retrieve(submission.job_id);
if (job.status === "Completed") {
console.log("Success:", job.result);
break;
} else if (job.status === "Failed") {
console.log("Failed:", job.error);
break;
}
await new Promise(resolve => setTimeout(resolve, 2000));
}
# Submit job
JOB_ID=$(curl -s -X POST "https://platform.reducto.ai/parse_async" \
-H "Authorization: Bearer $REDUCTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input": "https://example.com/document.pdf"}' | jq -r '.job_id')
echo "Job submitted: $JOB_ID"
# Poll until complete
while true; do
RESPONSE=$(curl -s "https://platform.reducto.ai/job/$JOB_ID" \
-H "Authorization: Bearer $REDUCTO_API_KEY")
STATUS=$(echo $RESPONSE | jq -r '.status')
if [ "$STATUS" = "Completed" ]; then
echo "Success"
echo $RESPONSE | jq '.result'
break
elif [ "$STATUS" = "Failed" ]; then
echo "Failed"
break
fi
sleep 2
done
Polling is straightforward but requires keeping a process running. For production systems processing many documents, webhooks are more efficient.
Priority processing
By default, synchronous (run()) jobs are prioritized over asynchronous (run_job()) jobs. This ensures interactive requests get fast responses while background jobs process when capacity is available.
You can request priority processing for async jobs if your account has priority budget available:
submission = client.parse.run_job(
input="urgent-document.pdf",
async_={
"priority": True
}
)
const submission = await client.parse.runJob({
input: "https://example.com/urgent-document.pdf",
async: {
priority: true
}
});
curl -X POST "https://platform.reducto.ai/parse_async" \
-H "Authorization: Bearer $REDUCTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "https://example.com/urgent-document.pdf",
"async": {"priority": true}
}'
Priority jobs are processed before non-priority async jobs but may still queue behind synchronous requests.
Async endpoints
Every Reducto endpoint has a corresponding async variant:
| Sync Endpoint | Async Endpoint | SDK Method |
|---|
POST /parse | POST /parse_async | client.parse.run_job() |
POST /extract | POST /extract_async | client.extract.run_job() |
POST /split | POST /split_async | client.split.run_job() |
POST /pipeline | POST /pipeline_async | client.pipeline.run_job() |
# Parse
parse_job = client.parse.run_job(input="https://example.com/document.pdf")
# Extract
extract_job = client.extract.run_job(
input="https://example.com/document.pdf",
instructions={"schema": your_schema}
)
# Split
split_job = client.split.run_job(
input="https://example.com/document.pdf",
split_description=[{"name": "Section A", "description": "..."}]
)
# Pipeline
pipeline_job = client.pipeline.run_job(
input="https://example.com/document.pdf",
pipeline_id="your_pipeline_id"
)
// Parse
const parseJob = await client.parse.runJob({ input: "https://example.com/document.pdf" });
// Extract
const extractJob = await client.extract.runJob({
input: "https://example.com/document.pdf",
instructions: { schema: yourSchema }
});
// Split
const splitJob = await client.split.runJob({
input: "https://example.com/document.pdf",
splitDescription: [{ name: "Section A", description: "..." }]
});
// Pipeline
const pipelineJob = await client.pipeline.runJob({
input: "document.pdf",
pipelineId: "your_pipeline_id"
});
# Parse async
curl -X POST "https://platform.reducto.ai/parse_async" \
-H "Authorization: Bearer $REDUCTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{"input": "https://example.com/document.pdf"}'
# Extract async
curl -X POST "https://platform.reducto.ai/extract_async" \
-H "Authorization: Bearer $REDUCTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "https://example.com/document.pdf",
"instructions": {"schema": {"field": "string"}}
}'
# Split async
curl -X POST "https://platform.reducto.ai/split_async" \
-H "Authorization: Bearer $REDUCTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "https://example.com/document.pdf",
"split_description": [{"name": "Section A", "description": "..."}]
}'
# Pipeline async
curl -X POST "https://platform.reducto.ai/pipeline_async" \
-H "Authorization: Bearer $REDUCTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "https://example.com/document.pdf",
"pipeline_id": "your_pipeline_id"
}'
Include metadata with your job submission to help identify and route results:
submission = client.parse.run_job(
input="https://example.com/document.pdf",
async_={
"metadata": {
"user_id": "user_123",
"document_type": "invoice",
"batch_id": "batch_456"
}
}
)
const submission = await client.parse.runJob({
input: "https://example.com/document.pdf",
async: {
metadata: {
userId: "user_123",
documentType: "invoice",
batchId: "batch_456"
}
}
});
curl -X POST "https://platform.reducto.ai/parse_async" \
-H "Authorization: Bearer $REDUCTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "https://example.com/document.pdf",
"async": {
"metadata": {
"user_id": "user_123",
"document_type": "invoice",
"batch_id": "batch_456"
}
}
}'
The metadata is included in webhook notifications, making it easy to match results back to your application context without maintaining a separate mapping.
When to use async
Use run() when:
- Processing single documents interactively
- Document size is small (under 20 pages)
- You need results immediately in the same request
- Testing and development
Use run_job() / async endpoints when:
- Processing many documents in parallel
- Documents are large or complex
- You want fire-and-forget with webhook notification
- Building batch processing pipelines
- Processing in background workers
Job Retention
Jobs are deleted after 12 hours. This is part of Reducto’s zero data retention (ZDR) policy. If you query a job ID from more than 12 hours ago, you’ll receive a “Job not found” error.
Default behavior: Job results are retained for 12 hours. After this window, you’ll need to reprocess the document.
For longer retention: Enable persist_results to keep results indefinitely:
result = client.parse.run(
input="document.pdf",
settings={"persist_results": True}
)
# This job's results will be stored indefinitely
const result = await client.parse.run({
input: "document.pdf",
settings: { persist_results: true }
});
// This job's results will be stored indefinitely
curl -X POST "https://platform.reducto.ai/parse" \
-H "Authorization: Bearer $REDUCTO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"input": "https://example.com/document.pdf",
"settings": {"persist_results": true}
}'
persist_results requires opting in to Reducto Studio. Contact support to enable this feature for your organization.
Best practice: Always store results in your own database when you receive them via polling or webhook, rather than relying on Reducto’s retention.
API Reference
See the full API documentation for async endpoints:
Svix Webhooks
Get notified when jobs complete instead of polling.
Batch Processing
Process many documents in parallel with run().
Chaining Endpoints
Reuse parsed documents across multiple calls.
Pipeline Basics
Bundle workflows into a single API call.